We have created a bunch of 'how-to' videos, to get you easily started with Umbraco. Learn how to build projects in just a couple of minutes. Easiest CMS in the world.
The Umbraco community is the best of its kind, be sure to visit, and if you have any questions, we’re sure that you can get your answers from the community.
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Channels/rsd.aspx b/WebCms/Umbraco/Channels/rsd.aspx
new file mode 100644
index 0000000..a047215
--- /dev/null
+++ b/WebCms/Umbraco/Channels/rsd.aspx
@@ -0,0 +1,20 @@
+
+<%@ Page Language="C#" AutoEventWireup="true" Inherits="System.Web.UI.Page" %>
+<%@ Import Namespace="Umbraco.Core.IO" %>
+
+
+
+ umbraco
+ http://umbraco.org/
+ http://<%=Request.ServerVariables["SERVER_NAME"]%>
+
+ <%=IOHelper.ResolveUrl(SystemDirectories.Umbraco) %>/channels.aspx" />
+ <%=IOHelper.ResolveUrl(SystemDirectories.Umbraco) %>/channels.aspx" />
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Channels/wlwmanifest.aspx b/WebCms/Umbraco/Channels/wlwmanifest.aspx
new file mode 100644
index 0000000..cad2f8f
--- /dev/null
+++ b/WebCms/Umbraco/Channels/wlwmanifest.aspx
@@ -0,0 +1,51 @@
+
+<%@ Page Language="C#" AutoEventWireup="true" Inherits="System.Web.UI.Page" %>
+<%@ Import Namespace="Umbraco.Core.IO" %>
+<%@ Import Namespace="umbraco" %>
+
+
+
+ http://umbraco.org/images/liveWriterIcon.png
+ http://umbraco.org/images/liveWriterWatermark.png
+ View your site/weblog
+ Edit your site/weblog
+ {blog-homepage-url}<%= IOHelper.ResolveUrl(SystemDirectories.Umbraco) %>/
+ {blog-homepage-url}<%= IOHelper.ResolveUrl(SystemDirectories.Umbraco)%>/actions/editContent.aspx?id={post-id}
+
+
+
+ WebLayout
+
+
+ Yes
+ Yes
+ Yes
+ No
+ 100
+ Yes
+ Yes
+ No
+ No
+ No
+ Yes
+ Yes
+
+
+
diff --git a/WebCms/Umbraco/ClientRedirect.aspx b/WebCms/Umbraco/ClientRedirect.aspx
new file mode 100644
index 0000000..2a10d4d
--- /dev/null
+++ b/WebCms/Umbraco/ClientRedirect.aspx
@@ -0,0 +1,64 @@
+<%@ Page Language="C#" AutoEventWireup="true" Inherits="Umbraco.Web.UI.Pages.UmbracoEnsuredPage" %>
+<%--
+ This page is required because we cannot reload the angular app with a changed Hash since it just detects the hash and doesn't reload.
+ So this is used purely for a full reload of an angular app with a changed hash.
+--%>
+
+
+
+ Redirecting...
+
+
+
+ Redirecting...
+
+
diff --git a/WebCms/Umbraco/Config/Create/UI.xml b/WebCms/Umbraco/Config/Create/UI.xml
new file mode 100644
index 0000000..3e2c60a
--- /dev/null
+++ b/WebCms/Umbraco/Config/Create/UI.xml
@@ -0,0 +1,270 @@
+
+
+
+ Template
+ /create/simple.ascx
+
+
+
+
+
+
+ Template
+ /create/simple.ascx
+
+
+
+
+
+ Macro
+ /create/simple.ascx
+
+
+
+
+
+ Macro
+ /create/simple.ascx
+
+
+
+
+
+ Macro
+ /create/xslt.ascx
+
+
+
+
+
+ Xslt
+ /create/xslt.ascx
+
+
+
+
+
+ User
+ /create/user.ascx
+
+
+
+
+
+
+ User
+ /create/simple.ascx
+
+
+
+
+
+ membergroup
+ /create/simple.ascx
+
+
+
+
+
+ Stylesheet
+ /create/simple.ascx
+
+
+
+
+
+ XSLT file
+ /create/xslt.ascx
+
+
+
+
+
+ member
+ /create/member.ascx
+
+
+
+
+
+ member
+ /create/member.ascx
+
+
+
+
+
+ membergroup
+ /create/simple.ascx
+
+
+
+
+
+ Stylesheet editor egenskab
+ /create/simple.ascx
+
+
+
+
+
+
+ Stylesheet editor egenskab
+ /create/simple.ascx
+
+
+
+
+
+ Dictionary editor egenskab
+ /create/simple.ascx
+
+
+
+
+
+ Dictionary editor egenskab
+ /create/simple.ascx
+
+
+
+
+
+
+ Language
+ /create/language.ascx
+
+
+
+
+
+ Language
+ /create/language.ascx
+
+
+
+
+
+
+
+
+
+
+ Scripting file
+ /create/DLRScripting.ascx
+
+
+
+
+
+ Macro
+ /create/DLRScripting.ascx
+
+
+
+
+
+ Scripting file
+ /create/DLRScripting.ascx
+
+
+
+
+
+ Macro
+ /create/DLRScripting.ascx
+
+
+
+
+
+ Script file
+ /create/script.ascx
+
+
+
+
+
+ Script file
+ /create/script.ascx
+
+
+
+
+
+
+ Macro
+ /create/script.ascx
+
+
+
+
+
+ Package
+ /create/simple.ascx
+
+
+
+
+
+ Package
+ /create/simple.ascx
+
+
+
+
+
+ Package
+ /create/simple.ascx
+
+
+
+
+
+ Package
+ /create/simple.ascx
+
+
+
+
+
+ User Types
+ /create/simple.ascx
+
+
+
+
+
+
+ Macro
+ /Create/PartialView.ascx
+
+
+
+
+
+
+ Macro
+ /Create/PartialViewMacro.ascx
+
+
+
+
+
+
+ Macro
+ /Create/PartialView.ascx
+
+
+
+
+
+
+ Macro
+ /Create/PartialViewMacro.ascx
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Config/Lang/cs.xml b/WebCms/Umbraco/Config/Lang/cs.xml
new file mode 100644
index 0000000..50d5c0d
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/cs.xml
@@ -0,0 +1,1049 @@
+
+
+
+ umbraco
+ http://umbraco.org
+
+
+ Kultura a názvy hostitelů
+ Historie změn
+ Prohlížet uzel
+ Změnit typ dokumentu
+ Kopírovat
+ Vytvořit
+ Vytvořit balíček
+ Odstranit
+ Deaktivovat
+ Vyprázdnit koš
+ Exportovat typ dokumentu
+ Importovat typ dokumentu
+ Importovat balíček
+ Editovat na stránce
+ Odhlásit
+ Přesunout
+ Upozornění
+ Veřejný přístup
+ Publikovat
+ Nepublikovat
+ Znovu načíst uzly
+ Znovu publikovat celý web
+ Práva
+ Vrátit starší verzi
+ Odeslat k publikování
+ Odeslat k překladu
+ Seřadit
+ Odeslat k publikování
+ Přeložit
+ Aktualizovat
+ Výchozí hodnota
+
+
+ Přístup zakázán.
+ Přidat novou doménu
+ odebrat
+ Neplatný uzel.
+ Neplatný tvar domény.
+ Doména už byla přiřazena.
+ Doména
+ Jazyk
+ Nová doména '%0%' byla vytvořena
+ Doména '%0%' je odstraněna
+ Doména '%0%' už byla přiřazena
+ Jednoúrovňové cesty v doménách jsou podporovány, např. "example.com/en". Měli byste se jich nicméně vyvarovat. Raději použijte nastavení kultury výše.]]>
+ Doména '%0%' byla aktualizována
+ Editace aktuálních domén
+ Dědit
+ Kultura
+ nebo dědění kultury po nadřazeném uzlu. Vztahuje se také
+ na aktivní uzel.]]>
+ Domény
+
+
+ Zobrazení pro
+
+
+ Vybrat
+ Vybrat aktuální složku
+ Dělat něco jiného
+ Tučně
+ Zrušit odsazení odstavce
+ Vložit formulářové pole
+ Vložit grafický nadpis
+ Editovat Html
+ Odsadit odstavec
+ Kurzíva
+ Zarovnat na střed
+ Zarovnat na levo
+ Zarovnat na pravo
+ Vložit odkaz
+ Vložit místní odkaz (kotvu)
+ Neuspořádaný seznam
+ Číslovaný seznam
+ Vložit makro
+ Vložit obrázek
+ Editovat vztahy
+ Uložit
+ Uložit a publikovat
+ Uložit a odeslat ke schválení
+ Náhled
+ Náhled je deaktivován, protože není přiřazena žádná šablona
+ Vybrat styl
+ Zobrazit styly
+ Vložit tabulku
+
+
+ Abyste změnili typ dokumentu pro zvolený obsah, nejprve jej vyberte ze seznamu typů platných pro tohle umístění.
+ Pak potvrďte a/nebo pozměňte mapování vlastností z aktuálního typu na nový a dejte Uložit.
+ Obsah byl znovu publikován.
+ Aktuální vlastnost
+ Aktuální typ
+ Typ dokumentu nemůže být změněn, neboť neexistují alternativy platné pro toto umístění.
+ Typ dokumentu byl změněn
+ Mapování vlastností
+ Mapování na vlastnost
+ Nová šablona
+ Nový typ
+ nic
+ Obsah
+ Vybrat nový typ dokumentu
+ Typ dokumentu pro zvolený obsah byl úspěšně změněný na [new type] a následující vlastnosti byly namapovány:
+ na
+ Nelze dokončit mapování vlastností, neboť nejméně jedna z vlastností má definováno více než jedno mapování.
+ Jsou zobrazeny pouze alternativní typy platné pro aktuální umístění.
+
+
+ O této stránce
+ Alias
+ (jak byste popsali obrázek přes telefon)
+ Alternativní adresy URL
+ Klikněte pro editaci položky
+ Vytvořeno uživatelem
+ Původní autor
+ Aktualizováno uživatelem
+ Vytvořeno
+ Datum/čas vytvoření tohoto dokumentu
+ Typ dokumentu
+ Editování
+ Datum odebrání
+ Tato položko byla změněna po publikování
+ Tato položka není publikována
+ Naposledy publikováno
+ Typ média
+ Odkaz na položky medií
+ Skupina členů
+ Role
+ Typ člena
+ Nevybráno žádné datum
+ Titulek stránky
+ Vlastnosti
+ Tento dokument je publikován, ale není viditelný, protože jeho rodič '%0%' publikován není
+ Jejda: tento dokument je publikován, ale není v mezipaměti (vnitřní chyba)
+ Publikovat
+ Stav publikování
+ Datum publikování
+ Datum ukončení publikování
+ Datum odebrání
+ Třídění je aktualizováno
+ Abyste uzly setřídili, jednoduše je přetáhněte anebo klikněte na jednu z hlaviček sloupce. Podržením "shift" nebo "control" při výběru můžete označit uzlů více.
+ Statistika
+ Titulek (volitelně)
+ Typ
+ Nepublikovat
+ Naposledy změněno
+ Datum/čas poslední změny dokumentu
+ Odebrat soubor(y)
+ URL adresa dokumentu
+ Člen skupin(y)
+ Není člen skupin(y)
+ Podřízené položky
+ Cíl
+
+
+ Klikněte pro nahrání
+ Upusťte soubory zde...
+
+
+ Kde chcete vytvořit nový %0%
+ Vytvořit položku pod
+ Vyberte typ a titulek
+ "typy dokumentů".]]>
+ "typy medií".]]>
+
+
+ Prohlédnout svůj web
+ - Skrýt
+ Jestli se umbraco neotevírá, možná budete muset povolit na tomto webu vyskakovací okna
+ byl otevřený v novém okně
+ Restart
+ Navštívit
+ Vítejte
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Název
+ Spravovat názvy hostitelů
+ Zavřít toto okno
+ Jste si jistí. že chcete odstranit
+ Jste si jistí, že chcete deaktivovat
+ Zatrhněte, prosím, tento box, abyste potvrdili odstranění %0% položek
+ Jste si jistí?
+ Jste si jistí?
+ Vyjmout
+ Editovat položku slovníku
+ Editovat jazyk
+ Vložit místní odkaz
+ Vložit znak
+ Vložit grafický titulek
+ Vložit obrázek
+ Vložit odkaz
+ Kliknout pro přidání makra
+ Vložit tabulku
+ Naposledy editováno
+ Odkaz
+ Místní odkaz:
+ Při používání místních odkazů vložte znak "#" před odkaz
+ Otevřít v novém okně?
+ Nastavení makra
+ Toto makro nemá žádné vlastnosti, které by bylo možno editovat
+ Vložit
+ Editovat oprávnění pro
+ Položky koše jsou nyní mazány. Nezavírejte, prosím, toto okno, dokud operace probíhá
+ Koš je nyní prázdný
+ Odebrání položek z koše způsobí jejich trvalé odstranění
+ regexlib.com má v tuto chvíli nějaké problémy, které jsou mimo naší kontrolu. Omlouváme se za vzniklé nepříjemnosti.]]>
+ Vyhledat regulární výraz pro přidání validace formulářového prvku. Například: 'email, 'PSČ' 'url'
+ Odstranit makro
+ Pole je vyžadování
+ Web je přeindexován
+ Mezipaměť webu byla obnovena. Všechen publikovaný obsah je nyní aktuální, zatímco nepublikovaný obsah zůstal nepublikovaný.
+ Mezipaměť webu bude obnovena. Všechen publikovaný obsah bude aktualizován, zatímco nepublikovaný obsah zůstane nepublikovaný.
+ Počet sloupců
+ Počet řádků
+ Nastavení ID zástupce Nastavením ID zástupce můžete do této šablony zavést obsah z podřízených šablon tak, že odkážete na toto ID při použití prvku <asp:content />.]]>
+ Vyberte id zástupce ze seznamu níže. Můžete vybírat pouze z ID ze šablony nadřazené tomuto formuláři.]]>
+ Klikněte na obrázek pro zobrazení v plné velikosti
+ Vybrat položku
+ Zobrazit položku mezipaměti
+
+
+ %0%' níže. Můžete přidat další jazyky v nabídce 'jazyky' nalevo.]]>
+ Název jazyka
+
+
+ Zadejte Vaše uživatelské jméno
+ Zadejte Vaše heslo
+ Pojmenujte %0%...
+ Zadejte jméno...
+ Pište pro vyhledání...
+ Pište pro filtrování...
+
+
+ Povolené podřízené typy uzlů
+ Vytvořit
+ Odstranit záložku
+ Popis
+ Nová záložka
+ Záložka
+ Miniatura
+ Povolit zobrazení jako seznam
+
+
+ Přidat předlohu
+ Databázový datový typ
+ GUID editoru vlastností
+ Editor vlastností
+ Tlačítka
+ Povolit rozšířené nastavení pro
+ Povolit kontextové menu
+ Největší výchozí rozměr pro vložené obrázky
+ Související stylopisy
+ Zobrazit jmenovku
+ Šířka a výška
+
+
+ Vaše data byla uložena, ale než budete moci publikovat tuto stránku, je třeba odstranit některé chyby:
+ Současný MemberShip Provider nepodporuje změnu hesla (EnablePasswordRetrieval musí mít hodnotu true)
+ %0% již existuje
+ Vyskytly se chyby:
+ Vyskytly se chyby:
+ Heslo musí být nejméně %0% znaků dlouhé a obsahovat nejméně %1% nealfanumerických znaků
+ %0% musí být celé číslo
+ Pole %0% na záložce %1% je povinné
+ %0% je povinné pole
+ %0% v %1% není ve správném formátu
+ %0% není ve správném formátu
+
+
+ Použití daného typu souboru bylo zakázáno adminitrátorem
+ UPOZORNĚNÍ! I když CodeMirror je dle konfigurace povolený, je zakázaný v Internet Exploreru, protože není dost stabilní.
+ Vyplňte, prosím, alias i název nového typu vlastností!
+ Vyskytl se problém při čtení/zápisu do určeného souboru nebo adresáře
+ Uveďte, prosím, titulek
+ Vyberte, prosím, typ
+ Chystáte se obrázek zvětšit více, než je jeho původní rozměr. Opravdu chcete pokračovat?
+ Chyba ve skriptu python
+ Skript python nebyl ještě uložen, protože obsahuje chyby
+ Počáteční uzel je odstraněný, kontaktujte, prosím, administrátora
+ Před změnou stylu označte, prosím, obsah
+ Žádne aktivní styly nejsou dostupné
+ Umístěte, prosím, kurzor nalevo od těch dvou buňek, které chcete sloučit
+ Nemužete rozdělit buňku, která nebyla sloučená.
+ Chyba ve zdroji XSLT
+ Soubor XSLT nebyl uložen, protože obsahoval chyby
+ V nastavení datového typu použitého pro tuto vlastnost je chyba, zkontrolujte, prosím, datový typ
+
+
+ O...
+ Akce
+ Akce
+ Přidat
+ Alias
+ Jste si jistí?
+ Okraj
+ o
+ Zrušit
+ Okraj buňky
+ Vybrat
+ Zavřít
+ Zavřít okno
+ Komentovat
+ Potvrdit
+ Zachovat proporce
+ Pokračovat
+ Kopírovat
+ Vytvořit
+ Databáse
+ Datum
+ Výchozí
+ Odstranit
+ Odstraněno
+ Odstraňování...
+ Vzhled
+ Rozměry
+ Dolů
+ Stáhnout
+ Editovat
+ Editováno
+ Prvky
+ Email
+ Chyba
+ Najít
+ Výška
+ Nápověda
+ Ikona
+ Import
+ Vnitřní okraj
+ Vložit
+ Instalovat
+ Vyrovnat
+ Jazyk
+ Rozvržení
+ Nahrávání
+ Zamčeno
+ Přihlášení
+ Odhlášení
+ Odhlášení
+ Makro
+ Přesunout
+ Více
+ Název
+ Nový
+ Následující
+ Ne
+ z
+ OK
+ Otevřít
+ nebo
+ Heslo
+ Cesta
+ ID zástupce
+ Moment, prosím...
+ Předchozí
+ Vlastnosti
+ Email pro obdržení formulářových dat
+ Koš
+ Zbývající
+ Přejmenovat
+ Obnovit
+ Povinné
+ Zopakovat
+ Oprávnění
+ Hledat
+ Server
+ Zobrazit
+ Zobrazit stránku při odeslání
+ Rozměr
+ Seřadit
+ Submit
+ Typ
+ Pro hledání pište...
+ Nahoru
+ Aktualizovat
+ Povýšit
+ Nahrání
+ Url
+ Uživatel
+ Uživatelské jméno
+ Hodnota
+ Pohled
+ Vítejte...
+ Šířka
+ Ano
+ Složka
+ Výsledky hledání
+ Reorder
+ I am done reordering
+
+
+ Background color
+ Bold
+ Text color
+ Font
+ Text
+
+
+ Page
+
+
+ Instalátor se nemůže připojit k databázi.
+ Nelze uložit soubor web.config. Modifikujte, prosím, připojovací řetězec manuálně.
+ Vyše databáze byla nalezena a je identifikována jako
+ Nastavení databáze
+ instalovat, abyste nainstalovali Umbraco %0% databázi
+ ]]>
+ následující pro pokračování.]]>
+ Databáze nenalezena! Zkontrolujte, prosím, že informace v "připojovacím řetězci" souboru "web.config" jsou správné.
+
Pro pokračování otevřete, prosím, soubor "web.config" (za pužití Visual Studia nebo Vašeho oblíbeného tedtového editoru), přejděte na jeho konec, přidejte připojovací řetězec pro Vaši databázi v klíčí nazvaném "umbracoDbDSN" a soubor uložte.
]]>
+
+ Pokud je to nezbytné, kontaktujte vašeho poskytovatele hostingu.
+ Jestliže instalujete na místní počítač nebo server, budete potřebovat informace od Vašeho systémového administrátora.]]>
+
+ Stiskněte tlačítko povýšit pro povýšení Vaší databáze na Umbraco %0%
+
+ Neobávejte se - žádný obsah nebude odstraněn a všechno bude fungovat jak má!
+
+ ]]>
+ Stiskněte Následující pro pokračování. ]]>
+ následující, pro pokračování konfiguračního průvodce]]>
+ Heslo výchozího uživatele musí být změněno!]]>
+ Výchozí uživatel byl deaktivován, nebo nemá přístup k umbracu!
Netřeba nic dalšího dělat. Klikněte na Následující pro pokračování.]]>
+ Heslo výchozího uživatele bylo úspěšně změněno od doby instalace!
Netřeba nic dalšího dělat. Klikněte na Následující pro pokračování.]]>
+ Heslo je změněno!
+
+ umbraco vytváří výchozího uživatele s uživatelským jménem ('admin') a heslem ('default'). Je důležité změnit toto heslo na jiné.
+
+
+ Tento krok zkontroluje heslo výchozího uživatele a doporučí, má-li být změněno.
+
+ ]]>
+ Mějte skvělý start, sledujte naše uváděcí videa
+ Kliknutím na tlačítko následující (nebo modifikováním umbracoConfigurationStatus v souboru web.config) přijímáte licenci tohoto software tak, jak je uvedena v poli níže. Upozorňujeme, že tato distribuce umbraca se skládá ze dvou různých licencí, open source MIT licence pro framework a umbraco freeware licence, která pokrývá UI.
+ Není nainstalováno.
+ Dotčené soubory a složky
+ Další informace o nastavování oprávnění pro umbraco zde
+ Musíte udělit ASP.NET oprávnění měnit následující soubory/složky
+ Vaše nastavení oprávnění je téměř dokonalé!
+ Můžete provozovat umbraco bez potíží, ale nebudete smět instalovat balíčky, které jsou doporučené pro plné využívání všech možností umbraca.]]>
+ Jak to vyřešit
+ Klikněte zde, chcete-li číst textovou verzi
+ výukové video o nastavovaní oprávnění pro složky umbraca, nebo si přečtěte textovou verzi.]]>
+ Vaše nastavení oprávnění může být problém!
+
+ Můžete provozovat umbraco bez potíží, ale nebudete smět vytvářet složky a instalovat balíčky, které jsou doporučené pro plné využívání všech možností umbraca.]]>
+ Vaše nastavení oprívnění není připraveno pro umbraco!
+
+ Abyste mohli umbraco provozovat, budete muset aktualizovat Vaše nastavení oprávnění.]]>
+ Vaše nastavení oprávnění je dokonalé!
+ Jste připraveni provozovat umbraco a instalovat balíčky!]]>
+ Řešení potíží se složkami
+ Následujte tento odkaz pro další informace o potížích s ASP.NET a vytvářením složek.
+ Nastavování oprávnění pro složky
+
+ Chci začít od nuly
+ zjistěte jak)
+ Stále se můžete později rozhodnout nainstalovat Runway. Za tím účelem navštivte Vývojářskou sekci a zvolte Balíčky.
+ ]]>
+ Právě jste vytvořili čistou platformu Umbraco. Co chcete dělat dále?
+ Runway je nainstalován
+
+ Toto je náš seznam doporučených modulů, vyberte ty, které chcete nainstalovat, nebo si prohlédněte úplný seznam modulů
+ ]]>
+ Doporučeno pouze pro zkušené uživatele
+ Chci začít s jednoduchým webem
+
+ "Runway" je jednoduchý web poskytující některé základní typy dokumentů a šablon. Instalátor pro Vás může Runway nainstalovat automaticky a Vy ho pak můžete jednoduše editovat, rozšířit anebo úplně odstranit. Není nezbytný a můžete bez problému provozovat Umbraco bez něj. Runway nicméně nabízí jednoduché základy založené na nejlepších praktikách tak, abyste mohli začít rychleji, než kdykoliv jindy. Rozhodnete-li se Runway si nainstalovat, můžete si volitelně vybrat základní stavební bloky zvané Moduly Runway a stránky Runway si tak vylepšit.
+
+
+ Runway obsahuje: Úvodní stránku, stránku Začínáme, stránku Instalace modulů.
+ Volitelné moduly: Horní navigace, Mapa webu, Kontakt, Galerie.
+
+ ]]>
+ Co je Runway
+ Krok 1/5: Přijetí licence
+ Krok 2/5: Konfigurace databáze
+ Krok 3/5: Ověřování oprávnění k souborům
+ Krok 4/5: Kontrola zabezpečení umbraca
+ Krok 5/5: Umbraco je připraveno a můžete začít
+ Děkujeme, že jeste si vybrali umbraco
+ Prohlédněte si svůj nový web
+ Nainstalovali jste Runway, tak proč se nepodívat, jak Váš nový web vypadá.]]>
+ Další pomoc a informace
+ Abyste získali pomoc od naší oceňované komunity, projděte si dokumentaci, nebo si pusťte některá videa zdarma o tom, jak vytvořit jednoduchý web, jak používat balíčky a rychlý úvod do terminologie umbraca]]>
+ Umbraco %0% je nainstalováno a připraveno k použití
+ soubor /web.config a upravit klíč AppSetting umbracoConfigurationStatus dole na hodnotu '%0%'.]]>
+ ihned začít kliknutím na tlačítko "Spustit Umbraco" níže. Jestliže je pro Vás umbraco nové,
+ spoustu zdrojů naleznete na naších stránkách "začínáme".]]>
+ Spustit Umbraco
+ Chcete-li spravovat Váš web, jednoduše přejděte do administrace umbraca a začněte přidávat obsah, upravovat šablony a stylopisy, nebo přidávat nové funkce]]>
+ Připojení k databázi selhalo.
+ Umbraco verze 3
+ Umbraco verze 4
+ Shlédnout
+ umbraca %0% jako čisté instalace nebo povýšením z 3.0.
+
]]>
+ [%0%] Upozornění o %1% na %2%
+ Upozornění
+
+
+
+ a výběrem balíčku. Balíčky umbraco mají obvykle přípony ".umb" nebo ".zip".
+ ]]>
+ Autor
+ Ukázka
+ Dokumentace
+ Meta data balíčku
+ Název balíčku
+ Balíček neobsahuje žádné položky
+
+ Můžete jej ze systému bezpečně odstranit kliknutím na "odebrat balíček" níže.]]>
+ Žádné aktualizace nejsou dostupné
+ Možnosti balíčku
+ Čti mě balíčku
+ Úložiště balíčku
+ Potvrdit odinstalování
+ Balíček byl odinstalován
+ Balíček byl úspěšně odinstalován
+ Odinstalovat balíček
+
+ Upozornění: všechny dokumenty, media atd. závislé na položkách, které odstraníte, přestanou pracovat a mohou vést k nestabilitě systému,
+ takže odinstalovávejte opatrně. Jste-li na pochybách, kontaktujte autora balíčku.]]>
+ Stáhnout aktualizaci z úložiště
+ Balíček s aktualizací
+ Pokyny pro aktualizaci
+ Pro tento balíček je dostupná aktualizace. Můžete si ji stáhnout přímo z úložiště balíčků umbraco.
+ Verze balíčku
+ Historie verzí balíčku
+ Zobrazit web balíčku
+
+
+ Vložit s úplným formatováním (nedoporučeno)
+ Text, který chcete vložit, obsahuje speciální znaky nebo formatování. Toto může být způsobeno kopirováním textu z Microsoft Wordu. Umbraco může odstranit speciální znaky nebo formatování, takže vložený obsah bude pro web vhodnější.
+ Vložit jako čistý text bez jakéhokoliv formátování
+ Vložit, ale odstranit formátování (doporučeno)
+
+
+ Ochrana prostřednictvím rolí
+ použijte členské skupiny umbraca.]]>
+ autentizaci prostřednictvím rolí]]>
+ Chybová stránka
+ Použita, když jsou lidé přihlášení, ale nemají přístup
+ Vyberte, jak omezit přístup k této stránce
+ %0% je nyní chráněna
+ Ochrana odebrána z %0%
+ Přihlašovací stránka
+ Vyberte stránku, která obsahuje přihlašovací formulář
+ Odstranit ochranu
+ Vyberte stránky, které obsahují přihlašovací formulář a chybová hlášení
+ Vyberte role, které mají přístup k této stránce
+ Nastavte přihlašovací jmého a heslo pro tuto stránku
+ Jednouživatelská ochrana
+ Jestliže chcete nastavit jenom jednoduchou ochranu prostřednictvím uživatelského jména a hesla
+
+
+
+
+
+
+
+
+ Zahrnout nepublikované podřízené stránky
+ Probíhá publikování - počkejte, prosím...
+ %0% ze %1% stránek bylo publikováno...
+ %0% byla publikována
+ %0% a podstránky byly publikovány
+ Publikovat %0% a všechny její podstránky
+ ok pro publikování %0% a tedy zveřejnění jejího obsahu.
+ Můžete publikovat tuto stránku a všechny její podstránky zatrhnutím publikovat všchny podstránky níže.
+ ]]>
+
+
+ Nenakonfigurovali jste žádné schválené barvy
+
+
+ Přidat vnější odkaz
+ Přidat vnitřní odkaz
+ Přidat
+ Nadpis
+ Vnitřní stránka
+ URL
+ Posunout dolů
+ Posunout nahoru
+ Otevřít v novém okně
+ Odebrat odkaz
+
+
+ Současná verze
+ Červený text nebude ve vybrané verzi zobrazen, zelený znamená přidaný].]]>
+ Dokument byl vrácen na starší verzi
+ Tohle zobrazuje vybranou verzi jako html, jestliže chcete vidět rozdíly mezi 2 verzemi najednou, použijte rozdílové zobrazení
+ Vrátit starší verzi
+ Vybrat verzi
+ Zobrazení
+
+
+ Editovat skriptovací soubor
+
+
+ Domovník
+ Obsah
+ Kurýr
+ Vývojář
+ Průvodce nastavením Umbraca
+ Media
+ Členové
+ Zpravodaje
+ Nastavení
+ Statistiky
+ Překlad
+ Uživatelé
+ Nápověda
+
+
+ Výchozí šablona
+ Klíč slovníku
+ Pro importování typu dokumentu vyhledejte soubor ".udt" ve svém počítači tak, že kliknete na tlačítko "Prohledat" a pak kliknete na "Import" (na následující obrazovce budete vyzváni k potvrzení)
+ Název nové záložky
+ Typ uzlu
+ Typ
+ Stylopis
+ Skript
+ Vlastnost stylopisu
+ Záložka
+ Název záložky
+ Záložky
+ Nadřazený typ obsahu povolen
+ Tento typ obsahu používá
+ jako nadřazený typ obsahu. Záložky z nadřazených typů obsahu nejsou zobrazeny a mohou byt editovány pouze na nadřazených typech obsahu samotných
+ Na této záložce nejsou definovány žádné vlastnosti. Pro vytvoření nové vlastnosti klikněte na odkaz "přidat novou vlastnost" nahoře.
+
+
+ Sort order
+ Creation date
+ Třídění bylo ukončeno.
+ Abyste nastavili, jak mají být položky seřazeny, přetáhněte jednotlivé z nich nahoru či dolů. Anebo klikněte na hlavičku sloupce pro setřídění celé kolekce
+ Během třídění nezavírejte toto okno]]>
+
+
+ Publikování bylo zrušeno doplňkem třetí strany
+ Typ vlastnosti už existuje
+ Typ vlastnosti vytvořen
+ Datový typ: %1%]]>
+ Typ vlastnosti odstraněn
+ Typ vlastnosti uložen
+ Záložka vytvořena
+ Záložka odstraněna
+ Záložka s id: %0% odstraněna
+ Stylopis nebyl uložen
+ Stylopis byl uložen
+ Stylopis byl uložen bez chyb
+ Datový typ byl uložen
+ Položka slovníku byla uložena
+ Publikování se nezdařilo, protože nadřazená stránka není publikována
+ Obsah byl publikován
+ a je viditelný na webu
+ Obsah byl uložen
+ Nezapomeňte na publikování, aby se změny projevily
+ Odeslat ke schválení
+ Změny byly odeslány ke schválení
+ Medium bylo uloženo
+ Medium bylo uloženo bez chyb
+ Člen byl uložen
+ Vlastnost stylopisu byla uložena
+ Stylopis byl uložen
+ Šablona byla uložena
+ Chyba při ukládání uživatele (zkontrolujte log)
+ Uživatel byl uložen
+ Typ uživatele byl uložen
+ Soubor nebyl uložen
+ soubor nemohl být uložen. Zkontrolujte, prosím, oprávnění k souboru
+ Soubor byl uložen
+ Soubor byl uložen bez chyb
+ Jazyk byl uložen
+ Skript python nebyl uložen
+ Skript python nemohl být uložen kvůli chybě
+ Skript python byl uložen
+ Ve skriptu python nejsou žádné chyby
+ Šablona nebyla uložena
+ Ujistěte se, prosím, že nemáte 2 šablony se stejným aliasem
+ Šablona byla uložena
+ Šablona byla uložena bez chyb!
+ XSLT nebyl uložen
+ XSLT obsahoval chybu
+ XSLT nemohl být uložen, zkontrolujte oprávnění k souboru
+ XSLT byl uložen
+ V XSLT nejsou žádné chyby
+ Publikování obsahu bylo zrušeno
+ Částečný pohled byl uložen
+ Částečný pohled byl uložen bez chyb!
+ Částečný pohled nebyl uložen
+ Při ukládání souboru došlo k chybě.
+
+
+ Používá CSS syntaxi např.: h1, .redHeader, .blueTex
+ Editovat stylopis
+ Editovat vlastnost stylopisu
+ Název, který identifikuje vlastnost stylu v editoru formátovaného textu
+ Náhled
+ Styly
+
+
+ Editovat šablonu
+ Vložit obsahovou oblast
+ Vložit zástupce obsahové oblasti
+ Vložit položku slovníku
+ Vložit makro
+ Vložit pole stránky umbraco
+ Nadřazená šablona
+ Rychlá příručka k šablonovým značkám umbraca
+ Šablona
+
+
+ Choose type of content
+ Choose a layout
+ Add a row
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Click to embed
+ Click to insert image
+ Image caption...
+ Write here...
+
+ Grid Layouts
+ Layouts are the overall work area for the grid editor, usually you only need one or two different layouts
+ Add Grid Layout
+ Adjust the layout by setting column widths and adding additional sections
+ Row configurations
+ Rows are predefined cells arranged horizontally
+ Add row configuration
+ Adjust the row by setting cell widths and adding additional cells
+ Columns
+ Total combined number of columns in the grid layout
+ Settings
+ Configure what settings editors can change
+ Styles
+ Configure what styling editors can change
+ Settings will only save if the entered json configuration is valid
+ Allow all editors
+ Allow all row configurations
+
+
+ Alternativní pole
+ Alternativní text
+ Velká a malá písmena
+ Kódování
+ Vybrat pole
+ Konvertovat
+ Nahrazuje nové řádky html tagem <br>
+ Vlastní pole
+ Ano, pouze datum
+ Formátovat jako datum
+ HTML kódování
+ Nahradí speciální znaky jejich HTML ekvivalentem.
+ Bude vloženo za hodnotou pole
+ Bude vloženo před hodnotou pole
+ Malá písmena
+ Nic
+ Vložit za polem
+ Vložit před polem
+ Rekurzivní
+ Odstranit tagy odstavce
+ Odstraní jakékoliv <P> na začátku a na konci textu
+ Standardní pole
+ Velká písmena
+ Kódování URL
+ Formátuje speciální znaky v URL adresách
+ Bude použito pouze pokud jsou pole nahoře prázdná
+ Toto pole bude použito pouze pokud je primární pole prázdné
+ Ano, s časem. Oddělovač:
+
+
+ Úlohy přidělené Vám
+ přidělené Vám. Chcete-li vidět podrobné zobrazení včetně komentářů, klikněte na "Podrobnosti" nebo pouze na název stránky.
+ Můžete také přímo stáhnout stránku jako XML kliknutím na odkaz "Stáhnout Xml".
+ Pro zavření překladatelské úlohy přejděte, prosím, na zobrazení Podrobnosti a klikněte na tlačítko "Zavřít".
+ ]]>
+ zavřít úlohu
+ Podrobnosti překladu
+ Stáhnout všechny překladatelské úlohy jako xml
+ Stáhnout xml
+ Stáhnout xml DTD
+ Pole
+ Zahrnout podstránky
+
+ [%0%] Překladatelská úloha pro %1%
+ Žádní uživatelé překladatelé nebyli nalezeni. Vytvořte, prosím, překladatele před tím, než začnete posílat obsah k překladu
+ Úlohy vytvořené Vámi
+ vytvořené Vámi. Chcete-li vidět podrobné zobrazení včetně komentářů, klikněte na "Podrobnosti" nebo pouze na název stránky. Můžete také přímo stáhnout stránku jako XML kliknutím na odkaz "Stáhnout Xml".
+ Pro zavření překladatelské úlohy přejděte, prosím, na zobrazení Podrobnosti a klikněte na tlačítko "Zavřít".
+ ]]>
+ Stránka '%0%' byla poslána k překladu
+ Poslat stránku '%0%' k překladu
+ Přiděleno uživatelem
+ Otevřené úlohy
+ Slov celkem
+ Přeložit do
+ Překlad hotov.
+ Klikutím níže můžete vidět stránky, které jste právě přeložili. Jestliže je nalezena originální stránka, dostanete srovnání 2 stránek.
+ Překlad selhal, xml soubor může být poškozený
+ Možnosti překladu
+ Překladatel
+ Nahrát xml překladu
+
+
+ Prohlížeč mezipaměti
+ Koš
+ Vytvořené balíčky
+ Datové typy
+ Slovník
+ Instalované balíčky
+ Instalovat téma
+ Instalovat startovní sadu
+ Jazyky
+ Instalovat místní balíček
+ Makra
+ Typy medií
+ Členové
+ Skupiny členů
+ Role
+ Typy členů
+ Typy dokumentů
+ Balíčky
+ Balíčky
+ Soubory python
+ Instalovat z úložiště
+ Instalovat Runway
+ Moduly Runway
+ Skriptovací soubory
+ Skripty
+ Stylopisy
+ Šablony
+ XSLT soubory
+
+
+ Nová aktualizace je připrvena
+ %0% je připraven, klikněte zde pro stažení
+ Žádné spojení se serverem
+ Chyba při kontrole aktualizace. Zkontrolujte, prosím, trasovací zásobník pro další informace
+
+
+ Administrátor
+ Pole kategorie
+ Změnit heslo
+ Změnit heslo
+ Potvrdit heslo
+ Můžete změnit své heslo pro přístup do administrace Umbraca vyplněním formuláře níže a kliknutím na tlačítko 'Změnit Heslo'
+ Kanál obsahu
+ Popis
+ Deaktivovat uživatele
+ Typ dokumentu
+ Editor
+ Výtah
+ Jazyk
+ Přihlašovací jméno
+ Úvodní uzel v knihovně medií
+ Sekce
+ Deaktivovat přistup k Umbracu
+ Heslo
+ Resetovat heslo
+ Vyše heslo bylo změněno!
+ Potvrďte, prosím, nové heslo
+ Zadejte Vaše nové heslo
+ Vaše nové heslo nesmí být prázdné!
+ Současné heslo
+ Neplatné současné heslo
+ Nové heslo a potvrzující heslo se liší. Zkuste to, prosím, znovu!
+ Potvrzující heslo není stejné jako nové heslo!
+ Nahradit oprávnění podřízených uzlů
+ Nyní měníte oprávnění pro stránky:
+ Vyberte stránky, pro které chcete měnit oprávnění
+ Prohledat všechny podřízené uzly
+ Úvodní uzel v obsahu
+ Uživatelské jméno
+ Oprávnění uživatele
+ Typ uživatele
+ Typy uživatelů
+ Spisovatel
+ Váš profil
+ Vaše nedávná historie
+ Relace vyprší za
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Config/Lang/da.xml b/WebCms/Umbraco/Config/Lang/da.xml
new file mode 100644
index 0000000..3e579f4
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/da.xml
@@ -0,0 +1,1141 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Tilføj domæne
+ Revisionsspor
+ Gennemse elementer
+ Skift dokumenttype
+ Kopier
+ Opret
+ Opret pakke
+ Slet
+ Deaktivér
+ Tøm papirkurv
+ Eksportér dokumenttype
+ Eksportér til .NET
+ Eksportér til .NET
+ Importér dokumenttype
+ Importér pakke
+ Redigér i Canvas
+ Log af
+ Flyt
+ Notificeringer
+ Offentlig adgang
+ Udgiv
+ Fortryd udgivelse
+ Genindlæs elementer
+ Genudgiv hele sitet
+ Gendan
+ Rettigheder
+ Fortryd ændringer
+ Send til udgivelse
+ Send til oversættelse
+ Sortér
+ Send til udgivelse
+ Oversæt
+ Opdatér
+ Standard værdi
+
+
+ Tilladelse nægtet.
+ Tilføj nyt domæne
+ fjern
+ Ugyldig node.
+ Ugyldigt domæne-format.
+ Domæne er allerede blevet tildelt.
+ Sprog
+ Domæne
+ Domænet '%0%' er nu oprettet og tilknyttet siden
+ Domænet '%0%' er nu slettet
+ Domænet '%0%' er oprettet
+ Domænet '%0%' er nu opdateret
+ eller rediger nuværende domæner
+ f.eks. ditdomaene.com, www.ditdomaene.com
+
+
+ For
+
+
+ Ryd valg
+ Vælg
+ Vælg nuværende mappe
+ Gør noget andet
+ Fed
+ Fortryd indryk afsnit
+ Indsæt formularfelt
+ Indsæt grafisk overskrift
+ Redigér Html
+ Indryk afsnit
+ Kursiv
+ Centrér
+ Venstrestil afsnit
+ Højrestil afsnit
+ Indsæt link
+ Indsæt lokalt link (anker)
+ Punktopstilling
+ Nummerorden
+ Indsæt makro
+ Indsæt billede
+ Redigér relationer
+ Tilbage til listen
+ Gem
+ Gem og udgiv
+ Gem og send til udgivelse
+ Gem listevisning
+ Se siden
+ Preview er deaktiveret fordi der ikke er nogen skabelon tildelt
+ Vælg formattering
+ Vis koder
+ Indsæt tabel
+ Generer modeller
+
+
+ For at skifte det valgte indholds dokumenttype, skal du først vælge en ny dokumenttype, som er gyldig på denne placering.
+ Kontroller derefter, at alle egenskaber bliver overført rigtigt til den nye dokumenttype, og klik derefter på Gem.
+ Indholdet er blevet genudgivet.
+ Nuværende egenskab
+ Nuværende type
+ Du kan ikke skifte dokumenttype, da der ikke er andre gyldige dokumenttyper på denne placering.
+ Dokumenttype skiftet
+ Overfør egenskaber
+ Overfør til egenskab
+ Ny skabelon
+ Ny type
+ ingen
+ Indhold
+ Vælg ny dokumenttype
+ Dokumenttypen på detvalgte indhold blev skiftet til [new type], og følgende egenskaber blev overført:
+ til
+ Overførsel af egenskaber kunne ikke fuldføres, da en eller flere egenskaber er indstillet til at blive overført mere end én gang.
+ Kun andre dokumenttyper, der er gyldige på denne placering, vises.
+
+
+ Udgivet
+ Om siden
+ Alias
+ (hvordan ville du f.eks. beskrive billedet via telefonen?)
+ Alternative links
+ Klik for at redigere dette punkt
+ Oprettet af
+ Oprindelig forfatter
+ Opdateret af
+ Oprettet den
+ Tidspunkt for oprettelse
+ Dokumenttype
+ Redigerer
+ Nedtagningsdato
+ Dette punkt er ændret siden udgivelsen
+ Dette punkt er endnu ikke udgivet
+ Sidst udgivet
+ Der er ingen elementer at vise på listen.
+ Medietype
+ Link til medie(r)
+ Medlemsgruppe
+ Rolle
+ Medlemstype
+ Ingen dato valgt
+ Sidetitel
+ Egenskaber
+ Dette dokument er udgivet, men ikke synligt da den overliggende side '%0%' ikke er udgivet!
+ Upd: dette dokument er udgiver, men er ikke i cachen (intern fejl)
+ Udgivet
+ Udgivelsesstatus
+ Udgivelsesdato
+ Dato for Fortryd udgivelse
+ Fjern dato
+ Sorteringrækkefølgen er opdateret
+ For at sortere, træk siderne eller klik på en af kolonnehovederne. Du kan vælge flere sider ved at holde "shift" eller "control" nede mens du vælger.
+ Statistik
+ Titel (valgfri)
+ Alternativ tekst (valgfri)
+ Type
+ Fortryd udgivelse
+ Sidst redigeret
+ Tidspunkt for seneste redigering
+ Fjern fil
+ Link til dokument
+ Medlem af grupper(ne)
+ Ikke medlem af grupper(ne)
+ Undersider
+ Åben i vindue
+
+
+ Klik for at uploade
+ Slip filerne her...
+ Link til medie
+ eller klik her for at vælge filer
+ Tilladte filtyper er kun
+ Maks filstørrelse er
+
+
+ Opret et nyt medlem
+ Alle medlemmer
+
+
+ Hvor ønsker du at oprette den nye %0%
+ Opret under
+ Vælg en type og skriv en titel
+ "dokument typer".]]>
+ "media typer".]]>
+ Dokument type uden skabelon
+ Ny mappe
+ Ny datatype
+
+
+ Til dit website
+ - Skjul
+ Hvis Umbraco ikke starter, kan det skyldes at din browser ikke tillader pop-up vinduer
+ er åbnet i nyt vindue
+ Genstart
+ Besøg
+ Velkommen
+
+
+ Bliv
+ Kassér ændringer
+ Du har ikke-gemte ændringer
+ Er du sikker på du vil navigere væk fra denne side? - du har ikke-gemte ændringer
+
+
+ Færdig
+
+ Slettede %0% element
+ Slettede %0% elementer
+ Slettede %0% ud af %1% element
+ Slettede %0% ud af %1% elementer
+
+ Udgav %0% element
+ Udgav %0% elementer
+ Udgav %0% ud af %1% element
+ Udgav %0% ud af %1% elementer
+
+ Fjernede %0% element fra udgivelse
+ Fjernede %0% elementer fra udgivelse
+ Fjernede %0% ud af %1% element fra udgivelse
+ Fjernede %0% ud af %1% elementer fra udgivelse
+
+ Flyttede %0% element
+ Flyttede %0% elementer
+ Flyttede %0% ud af %1% element
+ Flyttede %0% ud af %1% elementer
+
+ Kopierede %0% element
+ Kopierede %0% elementer
+ Kopierede %0% ud af %1% element
+ Kopierede %0% ud af %1% elementer
+
+
+ Navn på lokalt link
+ Rediger domæner
+ Luk denne dialog
+ Er du sikker på at du vil slette
+ Er du sikker på du vil deaktivere
+ Afkryds venligst denne boks for at bekræfte sletningen af %0% enhed(er)
+ Er du sikker på at du vil forlade Umbraco?
+ Er du sikker?
+ Klip
+ Rediger ordbogsnøgle
+ Rediger sprog
+ Indsæt lokalt link
+ Indsæt tegn
+ Indsæt grafisk overskrift
+ Indsæt billede
+ Indsæt link
+ Indsæt makro
+ Indsæt tabel
+ Sidst redigeret
+ Link
+ Internt link:
+ Ved lokalt link, indsæt da en "#" foran linket
+ Åben i nyt vindue?
+ Makroindstillinger
+ Denne makro har ingen egenskaber du kan redigere
+ Indsæt tekst
+ Rediger rettigheder for
+ Elementerne i papirkurven slettes. Luk venligst ikke dette vindue mens sletningen foregår
+ Papirkurven er nu tom
+ Når elementer slettes fra papirkurven, slettes de for altid
+ regexlib.com's webservice oplever i øjeblikket problemer, vi ikke har kontrol over. Beklager ulejligheden. ]]>
+ Søg efter et regulært udtryk for at tilføje validering til et formularfelt. Eksempel: 'email', 'zip-code', 'url'
+ Fjern makro
+ Obligatorisk
+ Sitet er genindekseret
+ Sitet er nu genudgivet
+ Websitets cache vil blive genopfrisket. Alt udgivet indhold vil blive opdateret, mens upubliceret indhold vil forblive upubliceret.
+ Antal kolonner
+ Antal rækker
+ Vælg et placeholder id Ved at sætte et id på en placeholder kan du indskyde indhold fra undertemplates ved at referere til dette ID vha. et <asp:content /> element.]]>
+ Vælg et placeholder id fra listen herunder. Du kan kun vælge id'er fra den nuværende masterskabelon.]]>
+ Klik på billedet for at se den fulde størrelse
+ Vælg
+ Se cache element
+ Opret mappe...
+
+ Relatér til original
+
+ Link til side
+
+ Åbner det linket dokument i et nyt vindue eller fane
+ Åbner det linket dokument i fuld visning af vinduet
+ Åbner det linket dokument i "parent frame"
+
+ Link til medie
+
+ Vælg medie
+ Vælg ikon
+ Vælg item
+ Vælg link
+ Vælg makro
+ Vælg indhold
+ Vælg medlem
+ Vælg medlemsgruppe
+
+ Der er ingen parametre for denne makro
+
+ Link dit
+ Fjern link fra dit
+
+ konto
+
+ Vælg editor
+
+
+ Rediger de forskellige sprogversioner for ordbogselementet '%0%' herunder. Du tilføjer flere sprog under 'sprog' i menuen til venstre
+ Kulturnavn
+
+
+ Indtast dit brugernavn
+ Indtast dit kodeord
+ Navngiv %0%...
+ Indtast navn...
+ Label...
+ Indtast beskrivelse
+ Søg...
+ Filtrer...
+ Indtast nøgleord (tryk på Enter efter hvert nøgleord)...
+
+
+ Tillad på rodniveau
+ Kun dokumenttyper med denne indstilling aktiveret oprettes i rodniveau under Inhold og Mediearkiv
+ Tilladte typer
+ Sammensætning af dokumenttyper
+ Opret
+ Slet fane
+ Beskrivelse
+ Ny fane
+ Fane
+ Thumbnail
+ Aktiver listevisning
+ Viser undersider i en søgbar liste, undersider vises ikke i indholdstræet
+ Nuværende listevisning
+ Den aktive listevisningsdatatype
+ Opret brugerdefineret listevisning
+ Fjern brugerdefineret listevisning
+
+
+ Tilføj førværdi
+ Database-datatype
+ Data Editor GUID
+ Visningskontrol
+ Knapper
+ Aktiver avancerede indstillinger for
+ Aktiver kontekstmenu
+ Maks. std. størrelse på indsatte billeder
+ Relaterede stylesheets
+ Vis label
+ Bredde og højde
+
+
+ Dine data er blevet gemt, men før du kan udgive denne side er der nogle fejl der skal rettes:
+ Den nuværende membership-provider understøtter ikke skift af kodeord (EnablePasswordRetrieval skal være true)
+ %0% der findes allerede
+ Der var fejl i dokumentet:
+ Der var fejl i formularen:
+ Kodeordet skal være på minimum %0% tegn og indeholde mindst %1% alfanumeriske karakterer
+ %0% skal være et heltal
+ %0% under %1% er et obligatorisk felt og skal udfyldes
+ %0% er et obligatorisk felt og skal udfyldes
+ %0% under %1% er ikke i et korrekt format
+ %0% er ikke i et korrekt format
+
+
+ Der skete en fejl på severen
+ Denne filttype er blevet deaktiveret af administratoren
+ OBS! Selvom CodeMirror er slået til i konfigurationen, så er den deaktiveret i Internet Explorer fordi den ikke er stabil nok.
+ Du skal udfylde både Alias & Navn på den nye egenskabstype!
+ Der mangler læse/skrive rettigheder til bestemte filer og mapper
+ Skriv venligst en titel
+ Du skal vælge en type
+ Du er ved at gøre billedet større end originalen. Det vil forringe kvaliteten af billedet. Ønsker du at fortsætte?
+ Fejl i Python-script
+ Python-scriptet er ikke blevet gemt, fordi det indeholder fejl
+ Startnode er slettet, kontakt systemadministrator
+ Du skal markere noget indhold, før du kan ændre stylen
+ Der er ingen aktive styles eller formatteringer på denne side
+ Du skal stå til venstre for de 2 celler du ønsker at samle!
+ Du kan ikke opdele en celle, som ikke allerede er delt.
+ Fejl i XSLT kode
+ Din XSLT er ikke opdateret, da det indeholdt en fejl
+ Der er et problem med den datatype, der bruges til denn egenskab. Kontroller konfigurationen og prøv igen.
+
+
+ Om
+ Handling
+ Muligheder
+ Tilføj
+ Alias
+ Er du sikker?
+ Tilbage
+ Kant
+ af
+ Fortryd
+ Celle margen
+ Vælg
+ Luk
+ Luk vindue
+ Kommentar
+ Bekræft
+ Behold proportioner
+ Fortsæt
+ Kopiér
+ Opret
+ Database
+ Dato
+ Standard
+ Slet
+ Slettet
+ Sletter...
+ Design
+ Dimensioner
+ Ned
+ Hent
+ Rediger
+ Redigeret
+ Elementer
+ Email
+ Fejl
+ Find
+ Højde
+ Hjælp
+ Ikon
+ Importer
+ Indre margen
+ Indsæt
+ Installér
+ Justering
+ Sprog
+ Layout
+ Henter
+ Låst
+ Log ind
+ Log af
+ Log ud
+ Makro
+ Flyt
+ Mere
+ Navn
+ Ny
+ Næste
+ Nej
+ af
+ OK
+ Åben
+ eller
+ Kodeord
+ Sti
+ Placeholder ID
+ Et øjeblik...
+ Forrige
+ Egenskaber
+ Email der skal modtage indhold af formular
+ Papirkurv
+ Din papirkurv er tom
+ Mangler
+ Omdøb
+ Forny
+ Påkrævet
+ Prøv igen
+ Rettigheder
+ Søg
+ Server
+ Vis
+ Hvilken side skal vises efter at formularen er sendt
+ Størrelse
+ Sortér
+ Indsend
+ Type
+ Skriv for at søge...
+ Op
+ Opdatér
+ Opdatér
+ Upload
+ Url
+ Bruger
+ Brugernavn
+ Værdi
+ Vis
+ Velkommen...
+ Bredde
+ Ja
+ Mappe
+ Søgeresultater
+ Sorter
+ Afslut sortering
+ Eksempel
+ Skift kodeord
+ til
+ Listevisning
+ Gemmer...
+ nuværende
+ Indlejring
+ valgt
+
+
+
+ Sort
+ Grøn
+ Gul
+ Orange
+ Blå
+ Rød
+
+
+
+ Tilføj fane
+ Tilføj egenskab
+ Tilføj editor
+ Tilføj skabelon
+ Tilføj child node
+ Tilføj child
+
+ Rediger datatype
+
+ Naviger sektioner
+
+ Genveje
+ Vis genveje
+
+ Brug listevisning
+ Tillad på rodniveau
+
+
+
+ Baggrundsfarve
+ Fed
+ Tekst farve
+ Skrifttype
+ Tekst
+
+
+ Side
+
+
+ Installeringsprogrammet kan ikke forbinde til databasen.
+ Kunne ikke gemme web.config filen. Du bedes venligst manuelt ændre database forbindelses strengen.
+ Din database er blevet fundet og identificeret som
+ Database konfiguration
+ installér knappen for at installere Umbraco %0% databasen
+ ]]>
+ installér knappen for at installere Umbraco %0% databasen]]>
+ Næste for at fortsætte.]]>
+ Databasen er ikke fundet. Kontrollér venligst at informationen i database forbindelsesstrengen i "web.config" filen er korrekt.
+
For at fortsætte bedes du venligst rette "web.config" filen (ved at bruge Visual Studio eller dit favoritprogram), scroll til bunden, tilføj forbindelsesstrengen til din database i feltet som hedder "umbracoDbDSN" og gem filen.
]]>
+ Kontakt venligst din ISP hvis det er nødvendigt. Hvis du installerer på en lokal maskine eller server kan du muligvis få informationerne fra din systemadministrator.]]>
+ Tryk på Opgradér knappen for at opgradere din database til Umbraco %0%
Bare rolig - intet indhold vil blive slettet og alt vil stadig fungere bagefter!
]]>
+ Tryk på Næste for at fortsætte.]]>
+ Næste for at fortsætte med konfigurationsguiden.]]>
+ Normalbrugerens adgangskode er nødt til at blive ændret!]]>
+ Normalbrugeren er blevet gjort utjenstdygtig eller har ikke adgang til Umbraco!
Du behøver ikke foretage yderligere handlinger. Tryk på Næste for at fortsætte.
]]>
+ Normalbrugerens adgangskode er på succesfuld vis blevet ændret siden installationen!
Du behøver ikke foretage yderligere handlinger. Tryk på Næste for at fortsætte.
]]>
+ Adgangskoden er blevet ændret!
+ Umbraco opretter en normalbruger med et login ('admin') og en adgangskode ('default'). Det er vigtigt at adgangskoden bliver ændret til noget unikt.
Dette skridt vil kontrollere normalbrugerens adgangskode og foreslå om det er nødt til at blive ændret.
]]>
+ Få en fremragende start, se vores videoer
+ Ved at klikke på næste knappen (eller ved at ændre UmbracoConfigurationStatus i web.config filen), accepterer du licensaftalen for denne software, som specificeret i boksen nedenfor. Bemærk venligst at denne Umbraco distribution består af to forskellige licenser, MIT's Open Source Licens for frameworket og Umbraco Freeware Licensen som dækker UI'en.
+ Endnu ikke installeret
+ Berørte filer og foldere
+ Flere informationer om at opsætte rettigheder for Umbraco her
+ Du er nødt til at give ASP.NET 'modify' rettigheder på følgende filer/foldere
+ Dine rettighedsinstillinger er næsten perfekte!
Du kan køre Umbraco uden problemer, men du vil ikke være i stand til at installere pakker, som er anbefalet for at få fuldt udbytte af Umbraco.]]>
+ Hvorledes besluttes
+ Klik her for at læse tekstversionen
+ video tutorials om at opsætte folderrettigheder for Umbraco eller læs tekstversionen.]]>
+ Dine rettighedsinstillinger kan være et problem!
Du kan afvikle Umbraco uden problemer, men du vil ikke være i stand til at oprette foldere eller installere pakker, hvilket er anbefalet for at få fuldt udbytte af Umbraco.]]>
+ Dine rettighedsinstillinger er ikke klar til Umbraco!
For at afvikle Umbraco er du nødt til at opdatere dine rettighedsinstillinger.]]>
+ Dine rettighedsinstillinger er perfekte!
Du er nu parat til at afvikle Umbraco og installere pakker!]]>
+ Løser folder problem
+ Følg dette link for mere information om udfordringer med ASP.NET og oprettelse af foldere
+ Sætter folderrettigheder op
+ Umbraco har behov for 'write/modify' adgang til bestemte foldere, for at kunne gemme filer som billeder og PDF'er. Umbraco gemmer også midlertidige data (eksempelvis cachen) for at forbedre ydelsen på dit website.
+ Jeg har lyst til at begynde på bar bund
+ lær hvordan) Du kan stadig vælge at installere Runway senere. Gå venligst til Udvikler-sektionen og vælg Pakker.]]>
+ Du har lige opsat en ren Umbraco-platform. Hvad ønsker du nu at gøre?
+ Runway er installeret
+ Dette er vores liste over anbefalede moduler. Kryds dem af du ønsker at installere eller se den fulde liste af moduler ]]>
+ Kun anbefalet for erfarne brugere
+ Jeg ønsker at begynder med et simpelt website
+ "Runway" er et simpelt website som stiller nogle basale dokumenttyper og skabeloner til rådighed. Instaleringsprogrammet kan automatisk opsætte Runway for dig, men du kan nemt redigere, udvide eller fjerne det. Det er ikke nødvendigt og du kan sagtens bruge Umbraco uden. Men Runway tilbyder et fundament, som er baseret på 'Best Practices', som får dig igang hurtigere end nogensinde før. Hvis du vælger at installere Runway, kan du efter eget valg vælge de grundlæggende byggesten kaldet 'Runway Modules' til at forbedre dine Runway-sider.
Inkluderet med Runway:Home Page, Getting Started page, Installing Modules page. Valgfri Moduler: Top Navigation, Sitemap, Contact, Gallery.
]]>
+ Hvad er Runway
+ Skridt 1/5: Acceptér licens
+ Skridt 2/5: Database-konfiguration
+ Skridt 3/5: Validerer filrettigheder
+ Skridt 4/5: Kontrollér Umbraco sikkerhed
+ Skridt 5/5: Umbraco er parat til at få dig igang
+ Tak fordi du valgte Umbraco
+ Gennemse dit nye site Du installerede Runway, så hvorfor ikke se hvordan dit nye website ser ud.]]>
+ Yderligere hjælpe og informationer Få hjælp fra vores prisvindende fællesskab, gennemse dokumentationen eller se nogle gratis videoer om hvordan du opsætter et simpelt site, hvordan du bruger pakker og en 'quick guide' til Umbraco terminologier]]>
+ Umbraco %0% er installeret og klar til brug
+ /web.config filen og opdatére 'AppSetting' feltet UmbracoConfigurationStatus i bunden til '%0%'.]]>
+ komme igang med det samme ved at klikke på "Start Umbraco" knappen nedenfor. Hvis du er ny med Umbraco, kan du finde masser af ressourcer på vores 'getting started' sider.
+]]>
+ Start UmbracoFor at administrere dit website skal du blot åbne Umbraco administrationen og begynde at tilføje indhold, opdatere skabelonerne og stylesheets'ene eller tilføje ny funktionalitet.]]>
+ Forbindelse til databasen fejlede.
+ Umbraco Version 3
+ Umbraco Version 4
+ Se
+ Umbraco %0% for en frisk installation eller for en opgradering fra version 3.0.
Klik her for at nulstille din adgangskode eller kopier/indsæt denne URL i din browser:
%1%
]]>
+
+
+
+ Skrivebord
+ Sektioner
+ Indhold
+
+
+ Vælg siden ovenover...
+ %0% er nu kopieret til %1%
+ Kopier til
+ %0% er nu flyttet til %1%
+ Flyt til
+ er blevet valgt som roden for dit nye indhold, klik 'ok' nedenunder.
+ Intet element valgt, vælg et element i listen ovenfor før der klikkes 'fortsæt'
+ Det nuværende element kan ikke lægges under denne pga. sin type
+ Det nuværende element kan ikke ligge under en af dens undersider
+ Dette element må ikke findes på rodniveau
+ Denne handling er ikke tilladt fordi du ikke har de fornødne rettigheder på et eller flere af under-dokumenterne
+ Relater det kopierede element til originalen
+
+
+ Rediger dine notificeringer for %0%
+
+
+ Hej %0%
+
Dette er en automatisk mail for at informere dig om at opgaven '%1%'
+ er blevet udførtpå siden '%2%' af brugeren '%3%'
]]>
+ [%0%] Notificering om %1% udført på %2%
+ Notificeringer
+
+
+ Vælg pakken fra din computer. Umbraco pakker er oftest en ".zip" fil
+ Udvikler
+ Demonstration
+ Dokumentation
+ Pakke meta data
+ Pakkenavn
+ Pakken indeholder ingen elementer
+ Du kan roligt fjerne denne fra systemet ved at klikke på "Fjern pakke" nedenfor.]]>
+ Ingen opdateringer tilgængelige
+ Pakkevalg
+ Pakke læs mig
+ Pakke opbevaringsbase
+ Bekræft af-installering
+ Pakken blev fjernet
+ Pakken er på succefuld vis blevet fjernet
+ Afinstallér pakke
+
+Bemærk: at dokumenter og medier som afhænger af denne pakke vil muligvis holde op med at virke, så vær forsigtig. Hvis i tvivl, kontakt personen som har udviklet pakken.]]>
+ Download opdatering fra opbevaringsbasen
+ Opdatér pakke
+ Opdateringsinstrukser
+ Der er en tilgængelig opdatering til denne pakke. Du kan downloade den direkte fra Umbracos pakke opbevaringsbase.
+ Pakke version
+ Se pakkeudviklerens website
+
+
+ Indsæt med fuld formattering (Anbefales ikke)
+ Den tekst du er ved at indsætte indeholder specialtegn eller formattering. Dette kan skyldes at du kopierer fra f.eks. Microsoft Word. Umbraco kan fjerne denne specialformattering automatisk så indholdet er mere velegnet til visning på en webside.
+ Indsæt som ren tekst, dvs. fjern al formattering
+ Indsæt, men fjern formattering som ikke bør være på en webside (Anbefales)
+
+
+ Rollebaseret beskyttelse
+ Hvis du ønsker at kontrollere adgang til siden ved hjælp af rollebaseret godkendelse via Umbracos medlemsgrupper.
+ rollebaseret godkendelse]]>
+ Fejlside
+ Brugt når folk er logget ind, men ingen adgang
+ Vælg hvordan siden skal beskyttes
+ %0% er nu beskyttet
+ Beskyttelse fjernet fra %0%
+ Log ind-side
+ Vælg siden der indeholder log ind-formularen
+ Fjern beskyttelse
+ Vælg siderne der indeholder log ind-formularer og fejlmeddelelser
+ Vælg de roller der har adgang til denne side
+ Indstil login og kodeord for denne side
+ Enkel brugerbeskyttelse
+ Hvis du blot ønsker at opsætte simpel beskyttelse ved hjælp af et enkelt login og kodeord
+
+
+ %0% kunne ikke udgives, fordi et 3. parts modul annullerede handlingen
+ Medtag ikke-udgivede undersider
+ Publicerer - vent venligst...
+ %0% ud af %1% sider er blevet udgivet...
+ %0% er nu publiceret
+ %0% og alle undersider er nu publiceret
+ Publicér alle undersider
+ ok for at udgive %0% og derved gøre indholdet offentligt tilgængeligt..
+]]>
+
+ [%0%] Benachrichtigung: %1% ausgeführt an Seite '%2%'
+ Benachrichtigungen
+
+
+ Wählen Sie ein Paket auf Ihrem lokalen Computer über "Datei auswählen" aus. <br />Umbraco-Pakete besitzen üblicherweise die Dateiendungen ".umb" oder ".zip".
+ Autor
+ Demonstration
+ Dokumentation
+ Paket-Meta-Daten
+ Name des Pakets
+ Paket enthält keine Elemente
+ Die Paket-Datei enthält keine Elemente die deinstalliert werden können.<br/><br/>Sie können das Paket ohne Gefahr deinstallieren indem Sie "Paket deinstallieren" anklicken.
+ Keine Updates für das Paket verfügbar
+ Paket-Optionen
+ Informationen zum Paket
+ Paket-Repository
+ Deinstallation bestätigen
+ Paket wurde deinstalliert
+ Das Paket wurde erfolgreich deinstalliert
+ Paket deinstallieren
+ Sie können einzelne Elemente, die Sie nicht deinstallieren möchten, unten abwählen. Wenn Sie "Deinstallation bestätigen" klicken, werden alle markierten Elemente entfernt.<br /><span style="color: Red; font-weight: bold;">Achtung:</span> alle Dokumente, Medien, etc, die von den zu entfernenden Elementen abhängen, werden nicht mehr funktionieren und im Zweifelsfall kann dass gesamte CMS instabil werden. Bitte deinstallieren Sie also mit Vorsicht. Falls Sie unsicher sind, kontaktieren Sie den Autor des Pakets.
+ Update vom Paket-Repository herunterladen
+ Paket-Update
+ Hinweise für die Durchführung des Updates
+ Es ist ein Update für dieses Paket verfügbar. Sie können es direkt vom Umbraco-Paket-Repository herunterladen.
+ Version des Pakets
+ Versionsverlauf des Pakets
+ Paket-Webseite aufrufen
+
+
+ Einfügen mit Formatierung (Nicht empfohlen)
+ Der Text, den Sie einfügen möchten, enthält Sonderzeichen oder spezielle Formatierungen. Dies kann zum Beispiel beim Kopieren aus Microsoft Word heraus passieren. Umbraco kann Sonderzeichen und spezielle Formatierungen automatisch entfernen, damit der eingefügte Inhalt besser für die Veröffentlichung im Web geeignet ist.
+ Als reinen Text ohne jede Formatierung einfügen
+ Einfügen, aber Formatierung bereinigen (Empfohlen)
+
+
+ Rollenbasierter Zugriffschutz
+ Wenn Sie rollenbasierte Authentifikation mit Umbraco-Mitgliedsgruppen verwenden wollen.
+ Sie müssen zuerst eine Mitgliedsgruppe erstellen, bevor derrollenbasierte Zugriffschutz aktiviert werden kann.
+ Fehlerseite
+ Seite mit Fehlermeldung (Benutzer-Login erfolgt, aber keinen Zugriff auf die aufgerufene Seite erlaubt)
+ Bitte wählen Sie, auf welche Art der Zugriff auf diese Seite geschützt werden soll
+ %0% ist nun zugriffsgeschützt
+ Zugriffsschutz von %0% entfernt
+ Login-Seite
+ Seite mit Login-Formular
+ Zugriffsschutz entfernen
+ Auswahl der Seiten, die das Login-Formular und die Fehlermeldung enthalten
+ Auswahl der Benutzerrollen, die Zugriff haben sollen
+ Kennwort und Login für diese Seite setzen
+ Zugriffsschutz durch einzelnen Benutzerzugang
+ Wenn Sie einen einfachen Zugriffsschutz unter Verwendung eines einzelnen Logins mit Kennwort aktivieren wollen
+
+
+ %0% kann nicht veröffentlicht werden, da die Veröffentlichung zeitlich geplant ist.
+ %0% konnte nicht veröffentlicht werden, da einige Daten die Gültigkeitsprüfung nicht bestanden haben.
+ %0% konnte nicht veröffentlicht werden, da ein Plug-In die Aktion abgebrochen hat.
+ %0% kann nicht veröffentlicht werden, da das übergeordnete Dokument nicht veröffentlicht ist.
+ Unveröffentlichte Unterelemente einschließen
+ Bitte warten, Veröffentlichung läuft...
+ %0% Elemente veröffentlicht, %1% Elemente ausstehend ...
+ %0% wurde veröffentlicht
+ %0% und die untergeordneten Elemente wurden veröffentlicht
+ %0% und alle untergeordneten Elemente veröffentlichen
+ Mit <em>Ok</em> wird <strong>%0%</strong> veröffentlicht und auf der Website sichtbar.<br/><br />Sie können dieses Element mitsamt seinen untergeordneten Elementen veröffentlichen, indem Sie <em>Unveröffentlichte Unterelemente einschließen</em> aktivieren.
+
+
+ Sie haben keine freigegeben Farben konfiguriert
+
+
+ Externen Link eingeben
+ Internen Link auswählen
+ Beschriftung
+ Link
+ In neuem Fenster öffnen
+ Bezeichnung eingeben
+ Link eingeben
+
+
+ Zurücksetzen
+
+
+ Aktuelle Version
+ Zeigt die Unterschiede zwischen der aktuellen und der ausgewählten Version an.<br />Text in <del>rot</del> fehlen in der ausgewählten Version, <ins>grün</ins> markierter Text wurde hinzugefügt.
+ Dokument wurde zurückgesetzt
+ Zeigt die ausgewählte Version als HTML an. Wenn Sie sich die Unterschiede zwischen zwei Versionen anzeigen lassen wollen, benutzen Sie bitte die Vergleichsansicht.
+ Zurücksetzen auf
+ Version auswählen
+ Ansicht
+
+
+ Skript bearbeiten
+
+
+ Umbraco Concierge
+ Inhalte
+ Umbraco Courier
+ Entwickler
+ Konfigurationsassistent
+ Medien
+ Mitglieder
+ Newsletter
+ Einstellungen
+ Statistiken
+ Übersetzung
+ Benutzer
+ Hilfe
+ Formulare
+ Auswertungen
+
+
+ go to
+ Hilfethemen zu
+ Video-Tutorials für
+ Die besten Umbraco-Video-Tutorials
+
+
+ Standardvorlage
+ Wörterbuch-Schlüsselwort
+ Wählen Sie die lokale .udt-Datei aus, die den zu importierenden Dokumenttyp enthält und fahren Sie mit dem Import fort. Die endgültige Übernahme erfolgt im Anschluss erst nach einer weiteren Bestätigung.
+ Beschriftung der neuen Registerkarte
+ Elementtyp
+ Typ
+ Stylesheet
+ Skript
+ Stylesheet-Eigenschaft
+ Registerkarte
+ Registerkartenbeschriftung
+ Registerkarten
+ Masterdokumenttyp aktiviert
+ Dieser Dokumenttyp verwendet
+ als Masterdokumenttyp. Register vom Masterdokumenttyp werden nicht angezeigt und können nur im Masterdokumenttyp selbst bearbeitet werden
+ Für dieses Register sind keine Eigenschaften definiert. Klicken Sie oben auf "neue Eigenschaft hinzufügen", um eine neue Eigenschaft hinzuzufügen.
+ Masterdokumenttyp
+ Zugehörige Vorlage anlegen
+
+
+ Sortierreihenfolge
+ Erstellungsdatum
+ Sortierung abgeschlossen.
+ Ziehen Sie die Elemente an ihre gewünschte neue Position.
+ Bitte warten, die Seiten werden sortiert. Das kann einen Moment dauern. Bitte schließen Sie dieses Fenster nicht, bis der Sortiervorgang abgeschlossen ist.
+
+
+ Fehlgeschlagen
+ Unzureichende Benutzerberechtigungen. Vorgang kann nicht abgeschlossen werden.
+ Abgebrochen
+ Vorgang wurde durch eine benutzerdefinierte Erweiterung abgebrochen
+ Das Veröffentlichen wurde von einem individuellen Ereignishandler abgebrochen
+ Eigenschaft existiert bereits
+ Eigenschaft erstellt
+ Name: %0% Datentyp: %1%
+ Eigenschaft gelöscht
+ Dokumenttyp gespeichert
+ Registerkarte erstellt
+ Registerkarte gelöscht
+ Registerkarte %0% gelöscht
+ Stylesheet wurde nicht gespeichert
+ Stylesheet gespeichert
+ Stylesheet erfolgreich gespeichert
+ Datentyp gespeichert
+ Wörterbucheintrag gespeichert
+ Veröffentlichung nicht möglich, da das übergeordnete Dokument nicht veröffentlicht ist.
+ Inhalte veröffentlicht
+ Sichtbar auf der Webseite
+ Inhalte gespeichert
+ Denken Sie daran, die Inhalte zu veröffentlichen, um die Änderungen sichtbar zu machen
+ Zur Abnahme eingereicht
+ Die Änderungen wurden zur Abnahme eingereicht
+ Medium gespeichert
+ Medium fehlerfrei gespeichert
+ Mitglied gespeichert
+ Stylesheet-Regel gespeichert
+ Stylesheet gespeichert
+ Vorlage gespeichert
+ Fehler beim Speichern des Benutzers.
+ Benutzer gespeichert
+ Benutzertyp gepsichert
+ Datei wurde nicht gespeichert
+ Datei konnte nicht gespeichert werden. Bitte überprüfen Sie die Schreibrechte auf Dateiebene.
+ Datei gespeichert
+ Datei erfolgreich gespeichert
+ Sprache gespeichert
+ Python-Skript nicht gespeichert
+ Das Python-Skript enthält Fehler
+ Python-Skript gespeichert
+ Keine Fehler im Python-Skript
+ Vorlage wurde nicht gespeichert
+ Bitte prüfen Sie, ob möglicherweise zwei Vorlagen den gleichen Alias verwenden.
+ Vorlage gespeichert
+ Vorlage erfolgreich gespeichert!
+ XSLT nicht gespeichert
+ Das XSLT enthält Fehler
+ XSLT kann nicht gespeichert werden. Bitte überprüfen Sie die Schreibrechte auf Dateiebene.
+ XSLT gespeichert
+ Keine Fehler im XSLT
+ Veröffentlichung des Inhalts aufgehoben
+ Partielle Ansicht gespeichert
+ Partielle Ansicht ohne Fehler gespeichert.
+ Partielle Ansicht nicht gespeichert
+ Fehler beim Speichern der Datei.
+ Skript gespeichert
+ Skript fehlerfrei gespeichert!
+ Skript nicht gespeichert
+ Fehler beim Speichern der Datei.
+ Fehler beim Speichern der Datei.
+
+
+ Gewünschter CSS-Selektor, zum Beispiel 'h1', '.bigHeader' oder 'p.infoText'
+ Stylesheet bearbeiten
+ Stylesheet-Regel bearbeiten
+ Bezeichnung im Auswahlmenü des Rich-Text-Editors
+ Vorschau
+ Stile
+
+
+ Vorlage bearbeiten
+ Platzhalter-Bereich verwenden
+ Platzhalter einfügen
+ Wörterbucheintrag einfügen
+ Makro einfügen
+ Umbraco-Feld einfügen
+ Mastervorlage
+ Schnellübersicht zu den verfügbaren Umbraco-Feldern
+ Vorlage
+
+
+ Element hinzufügen
+ Zeilenlayout auswählen
+ Einfach auf <i class="icon icon-add blue"></i> klicken, um das erste Element anzulegen
+ Drop content
+ Klicken, um Inhalt einzubetten
+ Klicken, um Abbildung einzufügen
+ Beschriftung ...
+ Hier schreiben ...
+ Layouts
+ Layouts sind die grundlegenden Arbeitsflächen für das Gestaltungsraster. Üblicherweise sind nicht mehr als ein oder zwei Layouts nötig.
+ Layout hinzufügen
+ Passen Sie das Layout an, indem Sie die Spaltenbreiten einstellen und Abschnitte hinzufügen.
+ Einstellungen für das Zeilenlayout
+ Zeilen sind vordefinierte horizontale Zellenanordnungen
+ Zeilenlayout hinzufügen
+ Pasen Sie das Zeilenlayout an, indem Sie die Zellenbreite einstellen und Zellen hinzufügen.
+ Spalten
+ Insgesamte Spaltenanzahl im Layout
+ Einstellungen
+ Legen Sie fest, welche Einstellungen die Autoren anpassen können.
+ CSS-Stile
+ Legen Sie fest, welche Stile die Autoren anpassen können.
+ Die Einstellungen werden nur gespeichert, wenn die angegebene JSON-Konfiguration gültig ist.
+ Alle Elemente erlauben
+ Alle Zeilenlayouts erlauben
+
+
+ Alternatives Feld
+ Alternativer Text
+ Groß- und Kleinschreibung
+ Kodierung
+ Feld auswählen
+ Zeilenumbrüche ersetzen
+ Ersetzt Zeilenumbrüche durch das HTML-Tag <br />
+ Benutzerdefinierte Felder
+ nur Datum
+ Als Datum formatieren
+ HTML kodieren
+ Wandelt Sonderzeichen in HTML-Zeichencodes um
+ Wird nach dem Feldinhalt eingefügt
+ Wird vor dem Feldinhalt eingefügt
+ Kleinbuchstaben
+ Keine
+ An den Feldinhalt anhängen
+ Dem Feldinhalt voranstellen
+ Rekursiv
+ Textabsatz entfernen
+ Alle <p> am Anfang und am Ende des Feldinhalts werden entfernt
+ Standardfelder
+ Großbuchstaben
+ URL kodieren
+ Wandelt Sonderzeichen zur Verwendung in URLs um
+ Wird nur verwendet, wenn beide vorgenannten Felder leer sind
+ Dieses Feld wird nur verwendet, wenn das primäre Feld leer ist
+ Datum und Zeit mit Trennzeichen:
+
+
+ Ihre Aufgaben
+ Die Liste unten zeigt <strong>ihre</strong> Übersetzungsaufträge. Um eine ausführliche Liste mit Kommentaren zu sehen, klicken Sie auf "Details" oder einfach auf den Seitennamen. Sie können die Seite auch direkt als XML herunterladen, indem Sie den Link "XML herunterladen" anklicken. <br/>Um eine Übersetzung abzuschließen, gehen Sie bitte auf die Detailansicht und klicken Sie auf "Aufgabe abschließen".
+ Aufgabe abschließen
+ Details zur Übersetzung
+ Alle Übersetzungsaufgaben als XML-Datei herunterladen
+ XML herunterladen
+ Herunterladen der XML-Defintionen (XML-DTD)
+ Felder
+ Einschließlich der Unterseiten
+
+Hallo %0%,
+
+das Dokument '%1%' wurde von '%2%' zur Übersetzung in '%5%' freigegeben.
+
+Zum Bearbeiten verwenden Sie bitte diesen Link: http://%3%/translation/details.aspx?id=%4%.
+
+Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/Umbraco.aspx
+
+Einen schönen Tag wünscht
+Ihr freundlicher Umbraco-Robot
+
+ [%0%] Aufgabe zur Übersetzung von '%1%'
+ Bitte erstellen Sie zuerst mindestens einen Übersetzer.
+ Von Ihnen erstellte Aufgaben
+ Die Liste unten zeigt die von <strong>Ihnen</strong> erstellten Seiten. Um eine ausführliche Liste mit Kommentaren zu sehen, klicken Sie auf "Details" oder einfach auf den Seitennamen. Sie können die Seite auch direkt als XML herunterladen, indem Sie den Link "XML herunterladen" anklicken. Um eine Übersetzung abzuschließen, gehen Sie bitte auf die Detailansicht und klicken Sie auf "Aufgabe abschließen".
+ Die Seite '%0%' wurde zur Übersetzung gesendet
+ Sendet die Seite '%0%' zur Übersetzung
+ Zugewiesen von
+ Aufgabe aktiviert
+ Anzahl der Wörter
+ Übersetzen in
+ Übersetzung abgeschlossen.
+ Sie können eine Vorschau der Seiten anzeigen, die Sie gerade übersetzt haben, indem Sie sie unten anklicken. Wenn die Originalseite zugeordnet werden kann, erhalten Sie einen Vergleich der beiden Seiten angezeigt.
+ Übersetzung fehlgeschlagen, die XML-Datei könnte beschädigt oder falsch formatiert sein
+ Übersetzungsoptionen
+ Übersetzer
+ Hochladen der XML-Übersetzungsdatei
+
+
+ Zwischenspeicher
+ Papierkorb
+ Erstellte Pakete
+ Datentypen
+ Wörterbuch
+ Installierte Pakete
+ Design-Skin installieren
+ Starter-Kit installieren
+ Sprachen
+ Lokales Paket hochladen und installieren
+ Makros
+ Medientypen
+ Mitglieder
+ Mitgliedergruppen
+ Mitgliederrollen
+ Mitglieder-Typen
+ Dokumententypen
+ Pakete
+ Pakete
+ Python-Dateien
+ Paket-Repositories
+ 'Runway' installieren
+ Runway-Module
+ Server-Skripte
+ Client-Skripte
+ Stylesheets
+ Vorlagen
+ XSLT-Dateien
+ Auswertungen
+
+
+ Neues Update verfügbar
+ %0% verfügbar, hier klicken zum Herunterladen
+ Keine Verbindung zum Update-Server
+ Fehler beim Überprüfen der Updates. Weitere Informationen finden Sie im Stacktrace.
+
+
+ Administrator
+ Feld für Kategorie
+ Kennwort ändern
+ Neues Kennwort
+ Neues Kennwort (Bestätigung)
+ Sie können Ihr Kennwort für den Zugriff auf den Umbraco-Verwaltungsbereich ändern, indem Sie das nachfolgende Formular ausfüllen und auf 'Kennwort ändern' klicken
+ Schnittstelle für externe Editoren
+ Feld für Beschreibung
+ Benutzer endgültig deaktivieren
+ Dokumenttyp
+ Editor
+ Feld für Textausschnitt
+ Sprache
+ Login
+ Startelement in der Medienbibliothek
+ Freigegebene Bereiche
+ Zugang sperren
+ Kennwort
+ Kennwort zurücksetzen
+ Ihr Kennwort wurde geändert!
+ Bitte bestätigen Sie das neue Kennwort
+ Geben Sie Ihr neues Kennwort ein
+ Ihr neues Kennwort darf nicht leer sein!
+ Aktuelles Kennwort
+ Aktuelles Kennwort falsch
+ Ihr neues Kennwort und die Wiederholung Ihres neuen Kennworts stimmen nicht überein. Bitte versuchen Sie es erneut!
+ Die Bestätigung Ihres Kennworts stimmt nicht mit dem angegebenen neuen Kennwort überein!
+ Die Berechtigungen der untergeordneten Elemente ersetzen
+ Die Berechtigungen für folgende Seiten werden angepasst:
+ Dokumente auswählen, um deren Berechtigungen zu ändern
+ Auch untergeordnete Elemente
+ Startelement in den Inhalten
+ Benutzername
+ Berechtigungen
+ Rolle
+ Rollen
+ Autor
+ Übersetzer
+ Ihr Profil
+ Ihr Verlauf
+ Sitzung läuft ab in
+
+
diff --git a/WebCms/Umbraco/Config/Lang/en.xml b/WebCms/Umbraco/Config/Lang/en.xml
new file mode 100644
index 0000000..44f00f4
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/en.xml
@@ -0,0 +1,1437 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Culture and Hostnames
+ Audit Trail
+ Browse Node
+ Change Document Type
+ Copy
+ Create
+ Create Package
+ Delete
+ Disable
+ Empty recycle bin
+ Export Document Type
+ Import Document Type
+ Import Package
+ Edit in Canvas
+ Exit
+ Move
+ Notifications
+ Public access
+ Publish
+ Unpublish
+ Reload
+ Republish entire site
+ Restore
+ Permissions
+ Rollback
+ Send To Publish
+ Send To Translation
+ Sort
+ Send to publication
+ Translate
+ Update
+ Default value
+
+
+ Permission denied.
+ Add new Domain
+ remove
+ Invalid node.
+ Invalid domain format.
+ Domain has already been assigned.
+ Language
+ Domain
+ New domain '%0%' has been created
+ Domain '%0%' is deleted
+ Domain '%0%' has already been assigned
+ Domain '%0%' has been updated
+ Edit Current Domains
+
+ Inherit
+ Culture
+ or inherit culture from parent nodes. Will also apply
+ to the current node, unless a domain below applies too.]]>
+ Domains
+
+
+ Viewing for
+
+
+ Clear selection
+ Select
+ Select current folder
+ Do something else
+ Bold
+ Cancel Paragraph Indent
+ Insert form field
+ Insert graphic headline
+ Edit Html
+ Indent Paragraph
+ Italic
+ Center
+ Justify Left
+ Justify Right
+ Insert Link
+ Insert local link (anchor)
+ Bullet List
+ Numeric List
+ Insert macro
+ Insert picture
+ Edit relations
+ Return to list
+ Save
+ Save and publish
+ Save and send for approval
+ Save list view
+ Preview
+ Preview is disabled because there's no template assigned
+ Choose style
+ Show styles
+ Insert table
+ Generate models
+
+
+ To change the document type for the selected content, first select from the list of valid types for this location.
+ Then confirm and/or amend the mapping of properties from the current type to the new, and click Save.
+ The content has been re-published.
+ Current Property
+ Current type
+ The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it.
+ Document Type Changed
+ Map Properties
+ Map to Property
+ New Template
+ New Type
+ none
+ Content
+ Select New Document Type
+ The document type of the selected content has been successfully changed to [new type] and the following properties mapped:
+ to
+ Could not complete property mapping as one or more properties have more than one mapping defined.
+ Only alternate types valid for the current location are displayed.
+
+
+ Is Published
+ About this page
+ Alias
+ (how would you describe the picture over the phone)
+ Alternative Links
+ Click to edit this item
+ Created by
+ Original author
+ Updated by
+ Created
+ Date/time this document was created
+ Document Type
+ Editing
+ Remove at
+ This item has been changed after publication
+ This item is not published
+ Last published
+ There are no items to show
+ There are no items to show in the list.
+ Media Type
+ Link to media item(s)
+ Member Group
+ Role
+ Member Type
+ No date chosen
+ Page Title
+ Properties
+ This document is published but is not visible because the parent '%0%' is unpublished
+ This document is published but is not in the cache
+ Could not get the url
+ This document is published but its url would collide with content %0%
+ Publish
+ Publication Status
+ Publish at
+ Unpublish at
+ Clear Date
+ Sortorder is updated
+ To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting
+ Statistics
+ Title (optional)
+ Alternative text (optional)
+ Type
+ Unpublish
+ Last edited
+ Date/time this document was edited
+ Remove file(s)
+ Link to document
+ Member of group(s)
+ Not a member of group(s)
+ Child items
+ Target
+ This translates to the following time on the server:
+ What does this mean?]]>
+
+
+ Click to upload
+ Drop your files here...
+ Link to media
+ or click here to choose files
+ Only allowed file types are
+ Max file size is
+
+
+ Create a new member
+ All Members
+
+
+ Where do you want to create the new %0%
+ Create an item under
+ Choose a type and a title
+ "document types".]]>
+ "media types".]]>
+ Document Type without a template
+ New folder
+ New data type
+
+
+ Browse your website
+ - Hide
+ If Umbraco isn't opening, you might need to allow popups from this site
+ has opened in a new window
+ Restart
+ Visit
+ Welcome
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Name
+ Manage hostnames
+ Close this window
+ Are you sure you want to delete
+ Are you sure you want to disable
+ Please check this box to confirm deletion of %0% item(s)
+ Are you sure?
+ Are you sure?
+ Cut
+ Edit Dictionary Item
+ Edit Language
+ Insert local link
+ Insert character
+ Insert graphic headline
+ Insert picture
+ Insert link
+ Click to add a Macro
+ Insert table
+ Last Edited
+ Link
+ Internal link:
+ When using local links, insert "#" in front of link
+ Open in new window?
+ Macro Settings
+ This macro does not contain any properties you can edit
+ Paste
+ Edit Permissions for
+ The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place
+ The recycle bin is now empty
+ When items are deleted from the recycle bin, they will be gone forever
+ regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]>
+ Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url'
+ Remove Macro
+ Required Field
+ Site is reindexed
+ The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished
+ The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished.
+ Number of columns
+ Number of rows
+ Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates,
+ by referring this ID using a <asp:content /> element.]]>
+ Select a placeholder id from the list below. You can only
+ choose Id's from the current template's master.]]>
+ Click on the image to see full size
+ Pick item
+ View Cache Item
+ Create folder...
+
+ Relate to original
+ The friendliest community
+
+ Link to page
+
+ Opens the linked document in a new window or tab
+ Opens the linked document in the full body of the window
+ Opens the linked document in the parent frame
+
+ Link to media
+
+ Select media
+ Select icon
+ Select item
+ Select link
+ Select macro
+ Select content
+ Select member
+ Select member group
+ No icons were found
+
+ There are no parameters for this macro
+
+ External login providers
+ Exception Details
+ Stacktrace
+ Inner Exception
+
+ Link your
+ Un-Link your
+
+ account
+
+ Select editor
+
+
+ %0%' below You can add additional languages under the 'languages' in the menu on the left
+ ]]>
+ Culture Name
+
+
+ Enter your username
+ Enter your password
+ Confirm your password
+ Name the %0%...
+ Enter a name...
+ Label...
+ Enter a description...
+ Type to search...
+ Type to filter...
+ Type to add tags (press enter after each tag)...
+ Enter your email
+
+
+ Allow at root
+ Only Content Types with this checked can be created at the root level of Content and Media trees
+ Allowed child node types
+ Document Type Compositions
+ Create
+ Delete tab
+ Description
+ New tab
+ Tab
+ Thumbnail
+ Enable list view
+ Configures the content item to show a sortable & searchable list of its children, the children will not be shown in the tree
+ Current list view
+ The active list view data type
+ Create custom list view
+ Remove custom list view
+
+
+ Add prevalue
+ Database datatype
+ Property editor GUID
+ Property editor
+ Buttons
+ Enable advanced settings for
+ Enable context menu
+ Maximum default size of inserted images
+ Related stylesheets
+ Show label
+ Width and height
+
+
+ Your data has been saved, but before you can publish this page there are some errors you need to fix first:
+ The current membership provider does not support changing password (EnablePasswordRetrieval need to be true)
+ %0% already exists
+ There were errors:
+ There were errors:
+ The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s)
+ %0% must be an integer
+ The %0% field in the %1% tab is mandatory
+ %0% is a mandatory field
+ %0% at %1% is not in a correct format
+ %0% is not in a correct format
+
+
+ Received an error from the server
+ The specified file type has been disallowed by the administrator
+ NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough.
+ Please fill both alias and name on the new property type!
+ There is a problem with read/write access to a specific file or folder
+ Error loading Partial View script (file: %0%)
+ Error loading userControl '%0%'
+ Error loading customControl (Assembly: %0%, Type: '%1%')
+ Error loading MacroEngine script (file: %0%)
+ "Error parsing XSLT file: %0%
+ "Error reading XSLT file: %0%
+ Please enter a title
+ Please choose a type
+ You're about to make the picture larger than the original size. Are you sure that you want to proceed?
+ Error in python script
+ The python script has not been saved, because it contained error(s)
+ Startnode deleted, please contact your administrator
+ Please mark content before changing style
+ No active styles available
+ Please place cursor at the left of the two cells you wish to merge
+ You cannot split a cell that hasn't been merged.
+ Error in XSLT source
+ The XSLT has not been saved, because it contained error(s)
+ There is a configuration error with the data type used for this property, please check the data type
+
+
+ About
+ Action
+ Actions
+ Add
+ Alias
+ All
+ Are you sure?
+ Back
+ Border
+ by
+ Cancel
+ Cell margin
+ Choose
+ Close
+ Close Window
+ Comment
+ Confirm
+ Constrain proportions
+ Continue
+ Copy
+ Create
+ Database
+ Date
+ Default
+ Delete
+ Deleted
+ Deleting...
+ Design
+ Dimensions
+ Down
+ Download
+ Edit
+ Edited
+ Elements
+ Email
+ Error
+ Find
+ Height
+ Help
+ Icon
+ Import
+ Inner margin
+ Insert
+ Install
+ Invalid
+ Justify
+ Language
+ Layout
+ Loading
+ Locked
+ Login
+ Log off
+ Logout
+ Macro
+ Mandatory
+ Move
+ More
+ Name
+ New
+ Next
+ No
+ of
+ OK
+ Open
+ or
+ Password
+ Path
+ Placeholder ID
+ One moment please...
+ Previous
+ Properties
+ Email to receive form data
+ Recycle Bin
+ Remaining
+ Rename
+ Renew
+ Required
+ Retry
+ Permissions
+ Search
+ Server
+ Show
+ Show page on Send
+ Size
+ Sort
+ Submit
+ Type
+ Type to search...
+ Up
+ Update
+ Upgrade
+ Upload
+ Url
+ User
+ Username
+ Value
+ View
+ Welcome...
+ Width
+ Yes
+ Folder
+ Search results
+ Reorder
+ I am done reordering
+ Preview
+ Change password
+ to
+ List view
+ Saving...
+ current
+ Embed
+ selected
+
+
+
+ Black
+ Green
+ Yellow
+ Orange
+ Blue
+ Red
+
+
+
+ Add tab
+ Add property
+ Add editor
+ Add template
+ Add child node
+ Add child
+
+ Edit data type
+
+ Navigate sections
+
+ Shortcuts
+ show shortcuts
+
+ Toggle list view
+ Toggle allow as root
+
+
+
+ Background colour
+ Bold
+ Text colour
+ Font
+ Text
+
+
+
+ Page
+
+
+ The installer cannot connect to the database.
+ Could not save the web.config file. Please modify the connection string manually.
+ Your database has been found and is identified as
+ Database configuration
+ install button to install the Umbraco %0% database
+ ]]>
+ Next to proceed.]]>
+ Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.
+
To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.
]]>
+
+ Please contact your ISP if necessary.
+ If you're installing on a local machine or server you might need information from your system administrator.]]>
+
+ Press the upgrade button to upgrade your database to Umbraco %0%
+
+ Don't worry - no content will be deleted and everything will continue working afterwards!
+
+ ]]>
+ Press Next to
+ proceed. ]]>
+ next to continue the configuration wizard]]>
+ The Default users' password needs to be changed!]]>
+ The Default user has been disabled or has no access to Umbraco!
No further actions needs to be taken. Click Next to proceed.]]>
+ The Default user's password has been successfully changed since the installation!
No further actions needs to be taken. Click Next to proceed.]]>
+ The password is changed!
+
+ Umbraco creates a default user with a login ('admin') and password ('default'). It's important that the password is
+ changed to something unique.
+
+
+ This step will check the default user's password and suggest if it needs to be changed.
+
+ ]]>
+ Get a great start, watch our introduction videos
+ By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI.
+ Not installed yet.
+ Affected files and folders
+ More information on setting up permissions for Umbraco here
+ You need to grant ASP.NET modify permissions to the following files/folders
+ Your permission settings are almost perfect!
+ You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
+ How to Resolve
+ Click here to read the text version
+ video tutorial on setting up folder permissions for Umbraco or read the text version.]]>
+ Your permission settings might be an issue!
+
+ You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
+ Your permission settings are not ready for Umbraco!
+
+ In order to run Umbraco, you'll need to update your permission settings.]]>
+ Your permission settings are perfect!
+ You are ready to run Umbraco and install packages!]]>
+ Resolving folder issue
+ Follow this link for more information on problems with ASP.NET and creating folders
+ Setting up folder permissions
+
+ I want to start from scratch
+ learn how)
+ You can still choose to install Runway later on. Please go to the Developer section and choose Packages.
+ ]]>
+ You've just set up a clean Umbraco platform. What do you want to do next?
+ Runway is installed
+
+ This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules
+ ]]>
+ Only recommended for experienced users
+ I want to start with a simple website
+
+ "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically,
+ but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However,
+ Runway offers an easy foundation based on best practices to get you started faster than ever.
+ If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.
+
+
+ Included with Runway: Home page, Getting Started page, Installing Modules page.
+ Optional Modules: Top Navigation, Sitemap, Contact, Gallery.
+
+ ]]>
+ What is Runway
+ Step 1/5 Accept license
+ Step 2/5: Database configuration
+ Step 3/5: Validating File Permissions
+ Step 4/5: Check Umbraco security
+ Step 5/5: Umbraco is ready to get you started
+ Thank you for choosing Umbraco
+ Browse your new site
+You installed Runway, so why not see how your new website looks.]]>
+ Further help and information
+Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]>
+ Umbraco %0% is installed and ready for use
+ /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]>
+ started instantly by clicking the "Launch Umbraco" button below. If you are new to Umbraco,
+you can find plenty of resources on our getting started pages.]]>
+ Launch Umbraco
+To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]>
+ Connection to database failed.
+ Umbraco Version 3
+ Umbraco Version 4
+ Watch
+ Umbraco %0% for a fresh install or upgrading from version 3.0.
+
Click here to reset your password or copy/paste this URL into your browser:
%1%
]]>
+
+
+
+ Dashboard
+ Sections
+ Content
+
+
+ Choose page above...
+ %0% has been copied to %1%
+ Select where the document %0% should be copied to below
+ %0% has been moved to %1%
+ Select where the document %0% should be moved to below
+ has been selected as the root of your new content, click 'ok' below.
+ No node selected yet, please select a node in the list above before clicking 'ok'
+ The current node is not allowed under the chosen node because of its type
+ The current node cannot be moved to one of its subpages
+ The current node cannot exist at the root
+ The action isn't allowed since you have insufficient permissions on 1 or more child documents.
+ Relate copied items to original
+
+
+ Edit your notification for %0%
+
+ Hi %0%
+
+
This is an automated mail to inform you that the task '%1%'
+ has been performed on the page '%2%'
+ by the user '%3%'
+
]]>
+ [%0%] Notification about %1% performed on %2%
+ Notifications
+
+
+
+ button and locating the package. Umbraco packages usually have a ".zip" extension.
+ ]]>
+ Author
+ Demonstration
+ Documentation
+ Package meta data
+ Package name
+ Package doesn't contain any items
+
+ You can safely remove this from the system by clicking "uninstall package" below.]]>
+ No upgrades available
+ Package options
+ Package readme
+ Package repository
+ Confirm uninstall
+ Package was uninstalled
+ The package was successfully uninstalled
+ Uninstall package
+
+ Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability,
+ so uninstall with caution. If in doubt, contact the package author.]]>
+ Download update from the repository
+ Upgrade package
+ Upgrade instructions
+ There's an upgrade available for this package. You can download it directly from the Umbraco package repository.
+ Package version
+ Package version history
+ View package website
+ Package already installed
+ This package cannot be installed, it requires a minimum Umbraco version of %0%
+ Uninstalling...
+ Downloading...
+ Importing...
+ Installing...
+ Restarting, please wait...
+ All done, your browser will now refresh, please wait...
+
+
+
+ Paste with full formatting (Not recommended)
+ The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.
+ Paste as raw text without any formatting at all
+ Paste, but remove formatting (Recommended)
+
+
+ Role based protection
+ using Umbraco's member groups.]]>
+ role-based authentication.]]>
+ Error Page
+ Used when people are logged on, but do not have access
+ Choose how to restrict access to this page
+ %0% is now protected
+ Protection removed from %0%
+ Login Page
+ Choose the page that contains the login form
+ Remove Protection
+ Select the pages that contain login form and error messages
+ Pick the roles who have access to this page
+ Set the login and password for this page
+ Single user protection
+ If you just want to setup simple protection using a single login and password
+
+
+
+
+
+
+
+
+
+ Include unpublished subpages
+ Publishing in progress - please wait...
+ %0% out of %1% pages have been published...
+ %0% has been published
+ %0% and subpages have been published
+ Publish %0% and all its subpages
+ Publish to publish %0% and thereby making its content publicly available.
+ You can publish this page and all its subpages by checking Include unpublished subpages below.
+ ]]>
+
+
+ You have not configured any approved colours
+
+
+ enter external link
+ choose internal page
+ Caption
+ Link
+ New window
+ Enter a new caption
+ Enter the link
+
+
+ Reset
+
+
+ Current version
+ Red text will not be shown in the selected version. , green means added]]>
+ Document has been rolled back
+ This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view
+ Rollback to
+ Select version
+ View
+
+
+ Edit script file
+
+
+ Concierge
+ Content
+ Courier
+ Developer
+ Umbraco Configuration Wizard
+ Media
+ Members
+ Newsletters
+ Settings
+ Statistics
+ Translation
+ Users
+ Help
+ Forms
+ Analytics
+
+
+ go to
+ Help topics for
+ Video chapters for
+ The best Umbraco video tutorials
+
+
+ Default template
+ Dictionary Key
+ To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen)
+ New Tab Title
+ Node type
+ Type
+ Stylesheet
+ Script
+ Stylesheet property
+ Tab
+ Tab Title
+ Tabs
+ Master Content Type enabled
+ This Content Type uses
+ as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself
+ No properties defined on this tab. Click on the "add a new property" link at the top to create a new property.
+ Master Document Type
+ Create matching template
+ Add icon
+
+
+ Sort order
+ Creation date
+ Sorting complete.
+ Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items
+ Do not close this window during sorting]]>
+
+
+ Validation
+ Validation errors must be fixed before the item can be saved
+ Failed
+ Insufficient user permissions, could not complete the operation
+ Cancelled
+ Operation was cancelled by a 3rd party add-in
+ Publishing was cancelled by a 3rd party add-in
+ Property type already exists
+ Property type created
+ DataType: %1%]]>
+ Propertytype deleted
+ Document Type saved
+ Tab created
+ Tab deleted
+ Tab with id: %0% deleted
+ Stylesheet not saved
+ Stylesheet saved
+ Stylesheet saved without any errors
+ Datatype saved
+ Dictionary item saved
+ Publishing failed because the parent page isn't published
+ Content published
+ and visible at the website
+ Content saved
+ Remember to publish to make changes visible
+ Sent For Approval
+ Changes have been sent for approval
+ Media saved
+ Media saved without any errors
+ Member saved
+ Stylesheet Property Saved
+ Stylesheet saved
+ Template saved
+ Error saving user (check log)
+ User Saved
+ User type saved
+ File not saved
+ file could not be saved. Please check file permissions
+ File saved
+ File saved without any errors
+ Language saved
+ Media Type saved
+ Member Type saved
+ Python script not saved
+ Python script could not be saved due to error
+ Python script saved
+ No errors in python script
+ Template not saved
+ Please make sure that you do not have 2 templates with the same alias
+ Template saved
+ Template saved without any errors!
+ XSLT not saved
+ XSLT contained an error
+ XSLT could not be saved, check file permissions
+ XSLT saved
+ No errors in XSLT
+ Content unpublished
+ Partial view saved
+ Partial view saved without any errors!
+ Partial view not saved
+ An error occurred saving the file.
+ Script view saved
+ Script view saved without any errors!
+ Script view not saved
+ An error occurred saving the file.
+ An error occurred saving the file.
+
+
+ Uses CSS syntax ex: h1, .redHeader, .blueTex
+ Edit stylesheet
+ Edit stylesheet property
+ Name to identify the style property in the rich text editor
+ Preview
+ Styles
+
+
+ Edit template
+ Insert content area
+ Insert content area placeholder
+ Insert dictionary item
+ Insert Macro
+ Insert Umbraco page field
+ Master template
+ Quick Guide to Umbraco template tags
+ Template
+
+
+ Choose type of content
+ Choose a layout
+ Add a row
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Click to embed
+ Click to insert image
+ Image caption...
+ Write here...
+
+ Grid Layouts
+ Layouts are the overall work area for the grid editor, usually you only need one or two different layouts
+ Add Grid Layout
+ Adjust the layout by setting column widths and adding additional sections
+ Row configurations
+ Rows are predefined cells arranged horizontally
+ Add row configuration
+ Adjust the row by setting cell widths and adding additional cells
+
+ Columns
+ Total combined number of columns in the grid layout
+
+ Settings
+ Configure what settings editors can change
+
+ Styles
+ Configure what styling editors can change
+
+ Settings will only save if the entered json configuration is valid
+
+ Allow all editors
+ Allow all row configurations
+ Set as default
+ Choose extra
+ Choose default
+ are added
+
+
+
+
+ Compositions
+ You have not added any tabs
+ Add new tab
+ Add another tab
+ Inherited from
+ Add property
+ Required label
+
+ Enable list view
+ Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree
+
+ Allowed Templates
+ Choose which templates editors are allowed to use on content of this type
+
+ Allow as root
+ Allow editors to create content of this type in the root of the content tree
+ Yes - allow content of this type in the root
+
+ Allowed child node types
+ Allow content of the specified types to be created underneath content of this type
+
+ Choose child node
+
+ Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists.
+ This content type is used in a composition, and therefore cannot be composed itself.
+ There are no content types available to use as a composition.
+
+ Available editors
+ Reuse
+ Editor settings
+
+ Configuration
+
+ Yes, delete
+
+ was moved underneath
+ was copied underneath
+ Select the folder to move
+ Select the folder to copy
+ to in the tree structure below
+
+ All Document types
+ All Documents
+ All media items
+
+ using this document type will be deleted permanently, please confirm you want to delete these as well.
+ using this media type will be deleted permanently, please confirm you want to delete these as well.
+ using this member type will be deleted permanently, please confirm you want to delete these as well
+
+ and all documents using this type
+ and all media items using this type
+ and all members using this type
+
+ using this editor will get updated with the new settings
+
+ Member can edit
+ Show on member profile
+
+
+
+
+ Alternative field
+ Alternative Text
+ Casing
+ Encoding
+ Choose field
+ Convert line breaks
+ Replaces line breaks with html-tag <br>
+ Custom Fields
+ Yes, Date only
+ Format as date
+ HTML encode
+ Will replace special characters by their HTML equivalent.
+ Will be inserted after the field value
+ Will be inserted before the field value
+ Lowercase
+ None
+ Insert after field
+ Insert before field
+ Recursive
+ Remove Paragraph tags
+ Will remove any <P> in the beginning and end of the text
+ Standard Fields
+ Uppercase
+ URL encode
+ Will format special characters in URLs
+ Will only be used when the field values above are empty
+ This field will only be used if the primary field is empty
+ Yes, with time. Separator:
+
+
+ Tasks assigned to you
+ assigned to you. To see a detailed view including comments, click on "Details" or just the page name.
+ You can also download the page as XML directly by clicking the "Download Xml" link.
+ To close a translation task, please go to the Details view and click the "Close" button.
+ ]]>
+ close task
+ Translation details
+ Download all translation tasks as XML
+ Download XML
+ Download XML DTD
+ Fields
+ Include subpages
+
+ [%0%] Translation task for %1%
+ No translator users found. Please create a translator user before you start sending content to translation
+ Tasks created by you
+ created by you. To see a detailed view including comments,
+ click on "Details" or just the page name. You can also download the page as XML directly by clicking the "Download Xml" link.
+ To close a translation task, please go to the Details view and click the "Close" button.
+ ]]>
+ The page '%0%' has been send to translation
+ Please select the language that the content should be translated into
+ Send the page '%0%' to translation
+ Assigned by
+ Task opened
+ Total words
+ Translate to
+ Translation completed.
+ You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages.
+ Translation failed, the XML file might be corrupt
+ Translation options
+ Translator
+ Upload translation XML
+
+
+ Cache Browser
+ Recycle Bin
+ Created packages
+ Data Types
+ Dictionary
+ Installed packages
+ Install skin
+ Install starter kit
+ Languages
+ Install local package
+ Macros
+ Media Types
+ Members
+ Member Groups
+ Roles
+ Member Types
+ Document Types
+ Relation Types
+ Packages
+ Packages
+ Python Files
+ Install from repository
+ Install Runway
+ Runway modules
+ Scripting Files
+ Scripts
+ Stylesheets
+ Templates
+ XSLT Files
+ Analytics
+
+
+ New update ready
+ %0% is ready, click here for download
+ No connection to server
+ Error checking for update. Please review trace-stack for further information
+
+
+ Administrator
+ Category field
+ Change Your Password
+ New password
+ Confirm new password
+ You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button
+ Content Channel
+ Description field
+ Disable User
+ Document Type
+ Editor
+ Excerpt field
+ Language
+ Username
+ Start Node in Media Library
+ Sections
+ Disable Umbraco Access
+ Old password
+ Password
+ Reset password
+ Your password has been changed!
+ Please confirm the new password
+ Enter your new password
+ Your new password cannot be blank!
+ Current password
+ Invalid current password
+ There was a difference between the new password and the confirmed password. Please try again!
+ The confirmed password doesn't match the new password!
+ Replace child node permissions
+ You are currently modifying permissions for the pages:
+ Select pages to modify their permissions
+ Search all children
+ Start Node in Content
+ Name
+ User permissions
+ User type
+ User types
+ Writer
+ Translator
+ Change
+ Your profile
+ Your recent history
+ Session expires in
+
+
+ Validation
+ Validate as email
+ Validate as a number
+ Validate as a Url
+ ...or enter a custom validation
+ Field is mandatory
+
+
+
+ Value is set to the recommended value: '%0%'.
+ Value was set to '%1%' for XPath '%2%' in configuration file '%3%'.
+ Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'.
+ Found unexpected value '%0%' for '%2%' in configuration file '%3%'.
+
+
+ Custom errors are set to '%0%'.
+ Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live.
+ Custom errors successfully set to '%0%'.
+
+ MacroErrors are set to '%0%'.
+ MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely when there's any errors in macros. Rectifying this will set the value to '%1%'.
+ MacroErrors are now set to '%0%'.
+
+
+ Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'.
+ Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%).
+ Try Skip IIS Custom Errors successfully set to '%0%'.
+
+
+ File does not exist: '%0%'.
+ '%0%' in config file '%1%'.]]>
+ There was an error, check log for full error: %0%.
+
+ Members - Total XML: %0%, Total: %1%, Total invalid: %2%
+ Media - Total XML: %0%, Total: %1%, Total invalid %2%
+ Content - Total XML: %0%, Total published: %1%, Total invalid %2%
+
+ Your site certificate was marked as valid.
+ Certificate validation error: '%0%'
+ Error pinging the URL %0% - '%1%'
+ You are currently %0% viewing the site using the HTTPS scheme.
+ The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'.
+ The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure.
+ Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0%
+
+
+ Enable HTTPS
+ Sets umbracoSSL setting to true in the appSettings of the web.config file.
+ The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure.
+
+ Fix
+ Cannot fix a check with a value comparison type of 'ShouldNotEqual'.
+ Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value.
+ Value to fix check not provided.
+
+ Debug compilation mode is disabled.
+ Debug compilation mode is currently enabled. It is recommended to disable this setting before go live.
+ Debug compilation mode successfully disabled.
+
+ Trace mode is disabled.
+ Trace mode is currently enabled. It is recommended to disable this setting before go live.
+ Trace mode successfully disabled.
+
+ All folders have the correct permissions set.
+
+ %0%.]]>
+ %0%. If they aren't being written to no action need be taken.]]>
+
+ All files have the correct permissions set.
+
+ %0%.]]>
+ %0%. If they aren't being written to no action need be taken.]]>
+
+ X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]>
+ X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]>
+ Set Header in Config
+ Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites.
+ A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file.
+ Could not update web.config file. Error: %0%
+
+
+ %0%.]]>
+ No headers revealing information about the website technology were found.
+
+ In the Web.config file, system.net/mailsettings could not be found.
+ In the Web.config file system.net/mailsettings section, the host is not configured.
+ SMTP settings are configured correctly and the service is operating as expected.
+ The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct.
+
+ %0%.]]>
+ %0%.]]>
+
+
+ Disable URL tracker
+ Enable URL tracker
+ Original URL
+ Redirected To
+ No redirects have been made
+ When a published page gets renamed or moved a redirect will automatically be made to the new page.
+ Remove
+ Are you sure you want to remove the redirect from '%0%' to '%1%'?
+ Redirect URL removed.
+ Error removing redirect URL.
+ Are you sure you want to disable the URL tracker?
+ URL tracker has now been disabled.
+ Error disabling the URL tracker, more information can be found in your log file.
+ URL tracker has now been enabled.
+ Error enabling the URL tracker, more information can be found in your log file.
+
+
diff --git a/WebCms/Umbraco/Config/Lang/en_us.xml b/WebCms/Umbraco/Config/Lang/en_us.xml
new file mode 100644
index 0000000..3e998cc
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/en_us.xml
@@ -0,0 +1,1442 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Culture and Hostnames
+ Audit Trail
+ Browse Node
+ Change Document Type
+ Copy
+ Create
+ Create Package
+ Delete
+ Disable
+ Empty recycle bin
+ Export Document Type
+ Import Document Type
+ Import Package
+ Edit in Canvas
+ Exit
+ Move
+ Notifications
+ Public access
+ Publish
+ Unpublish
+ Reload
+ Republish entire site
+ Restore
+ Permissions
+ Rollback
+ Send To Publish
+ Send To Translation
+ Sort
+ Send to publication
+ Translate
+ Update
+ Default value
+
+
+ Permission denied.
+ Add new Domain
+ remove
+ Invalid node.
+ Invalid domain format.
+ Domain has already been assigned.
+ Language
+ Domain
+ New domain '%0%' has been created
+ Domain '%0%' is deleted
+ Domain '%0%' has already been assigned
+ Domain '%0%' has been updated
+ Edit Current Domains
+
+ Inherit
+ Culture
+ or inherit culture from parent nodes. Will also apply
+ to the current node, unless a domain below applies too.]]>
+ Domains
+
+
+ Viewing for
+
+
+ Clear selection
+ Select
+ Select current folder
+ Do something else
+ Bold
+ Cancel Paragraph Indent
+ Insert form field
+ Insert graphic headline
+ Edit Html
+ Indent Paragraph
+ Italic
+ Center
+ Justify Left
+ Justify Right
+ Insert Link
+ Insert local link (anchor)
+ Bullet List
+ Numeric List
+ Insert macro
+ Insert picture
+ Edit relations
+ Return to list
+ Save
+ Save and publish
+ Save and send for approval
+ Save list view
+ Preview
+ Preview is disabled because there's no template assigned
+ Choose style
+ Show styles
+ Insert table
+ Generate models
+ Save and generate models
+
+
+ To change the document type for the selected content, first select from the list of valid types for this location.
+ Then confirm and/or amend the mapping of properties from the current type to the new, and click Save.
+ The content has been re-published.
+ Current Property
+ Current type
+ The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it.
+ Document Type Changed
+ Map Properties
+ Map to Property
+ New Template
+ New Type
+ none
+ Content
+ Select New Document Type
+ The document type of the selected content has been successfully changed to [new type] and the following properties mapped:
+ to
+ Could not complete property mapping as one or more properties have more than one mapping defined.
+ Only alternate types valid for the current location are displayed.
+
+
+ Is Published
+ About this page
+ Alias
+ (how would you describe the picture over the phone)
+ Alternative Links
+ Click to edit this item
+ Created by
+ Original author
+ Updated by
+ Created
+ Date/time this document was created
+ Document Type
+ Editing
+ Remove at
+ This item has been changed after publication
+ This item is not published
+ Last published
+ There are no items to show
+ There are no items to show in the list.
+ Media Type
+ Link to media item(s)
+ Member Group
+ Role
+ Member Type
+ No date chosen
+ Page Title
+ Properties
+ This document is published but is not visible because the parent '%0%' is unpublished
+ This document is published but is not in the cache
+ Could not get the url
+ This document is published but its url would collide with content %0%
+ Publish
+ Publication Status
+ Publish at
+ Unpublish at
+ Clear Date
+ Sortorder is updated
+ To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting
+ Statistics
+ Title (optional)
+ Alternative text (optional)
+ Type
+ Unpublish
+ Last edited
+ Date/time this document was edited
+ Remove file(s)
+ Link to document
+ Member of group(s)
+ Not a member of group(s)
+ Child items
+ Target
+ This translates to the following time on the server:
+ What does this mean?]]>
+
+
+ Click to upload
+ Drop your files here...
+ Link to media
+ or click here to choose files
+ Only allowed file types are
+ Cannot upload this file, it does not have an approved file type
+ Max file size is
+
+
+ Create a new member
+ All Members
+
+
+ Where do you want to create the new %0%
+ Create an item under
+ Choose a type and a title
+ "document types".]]>
+ "media types".]]>
+ Document Type without a template
+ New folder
+ New data type
+
+
+ Browse your website
+ - Hide
+ If Umbraco isn't opening, you might need to allow popups from this site
+ has opened in a new window
+ Restart
+ Visit
+ Welcome
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Name
+ Manage hostnames
+ Close this window
+ Are you sure you want to delete
+ Are you sure you want to disable
+ Please check this box to confirm deletion of %0% item(s)
+ Are you sure?
+ Are you sure?
+ Cut
+ Edit Dictionary Item
+ Edit Language
+ Insert local link
+ Insert character
+ Insert graphic headline
+ Insert picture
+ Insert link
+ Click to add a Macro
+ Insert table
+ Last Edited
+ Link
+ Internal link:
+ When using local links, insert "#" in front of link
+ Open in new window?
+ Macro Settings
+ This macro does not contain any properties you can edit
+ Paste
+ Edit Permissions for
+ The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place
+ The recycle bin is now empty
+ When items are deleted from the recycle bin, they will be gone forever
+ regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]>
+ Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url'
+ Remove Macro
+ Required Field
+ Site is reindexed
+ The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished
+ The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished.
+ Number of columns
+ Number of rows
+ Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates,
+ by referring this ID using a <asp:content /> element.]]>
+ Select a placeholder id from the list below. You can only
+ choose Id's from the current template's master.]]>
+ Click on the image to see full size
+ Pick item
+ View Cache Item
+ Create folder...
+
+ Relate to original
+ The friendliest community
+
+ Link to page
+
+ Opens the linked document in a new window or tab
+ Opens the linked document in the full body of the window
+ Opens the linked document in the parent frame
+
+ Link to media
+
+ Select media
+ Select icon
+ Select item
+ Select link
+ Select macro
+ Select content
+ Select member
+ Select member group
+
+ There are no parameters for this macro
+
+ External login providers
+ Exception Details
+ Stacktrace
+ Inner Exception
+
+ Link your
+ Un-Link your
+
+ account
+
+ Select editor
+
+
+ %0%' below You can add additional languages under the 'languages' in the menu on the left
+ ]]>
+ Culture Name
+
+
+ Enter your username
+ Enter your password
+ Confirm your password
+ Name the %0%...
+ Enter a name...
+ Label...
+ Enter a description...
+ Type to search...
+ Type to filter...
+ Type to add tags (press enter after each tag)...
+ Enter your email
+
+
+
+ Allow at root
+ Only Content Types with this checked can be created at the root level of Content and Media trees
+ Allowed child node types
+ Document Type Compositions
+ Create
+ Delete tab
+ Description
+ New tab
+ Tab
+ Thumbnail
+ Enable list view
+ Configures the content item to show a sortable & searchable list of its children, the children will not be shown in the tree
+ Current list view
+ The active list view data type
+ Create custom list view
+ Remove custom list view
+
+
+ Add prevalue
+ Database datatype
+ Property editor GUID
+ Property editor
+ Buttons
+ Enable advanced settings for
+ Enable context menu
+ Maximum default size of inserted images
+ Related stylesheets
+ Show label
+ Width and height
+
+
+ Your data has been saved, but before you can publish this page there are some errors you need to fix first:
+ The current membership provider does not support changing password (EnablePasswordRetrieval need to be true)
+ %0% already exists
+ There were errors:
+ There were errors:
+ The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s)
+ %0% must be an integer
+ The %0% field in the %1% tab is mandatory
+ %0% is a mandatory field
+ %0% at %1% is not in a correct format
+ %0% is not in a correct format
+
+
+ Received an error from the server
+ The specified file type has been disallowed by the administrator
+ NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough.
+ Please fill both alias and name on the new property type!
+ There is a problem with read/write access to a specific file or folder
+ Error loading Partial View script (file: %0%)
+ Error loading userControl '%0%'
+ Error loading customControl (Assembly: %0%, Type: '%1%')
+ Error loading MacroEngine script (file: %0%)
+ "Error parsing XSLT file: %0%
+ "Error reading XSLT file: %0%
+ Please enter a title
+ Please choose a type
+ You're about to make the picture larger than the original size. Are you sure that you want to proceed?
+ Error in python script
+ The python script has not been saved, because it contained error(s)
+ Startnode deleted, please contact your administrator
+ Please mark content before changing style
+ No active styles available
+ Please place cursor at the left of the two cells you wish to merge
+ You cannot split a cell that hasn't been merged.
+ Error in XSLT source
+ The XSLT has not been saved, because it contained error(s)
+ There is a configuration error with the data type used for this property, please check the data type
+
+
+ About
+ Action
+ Actions
+ Add
+ Alias
+ All
+ Are you sure?
+ Back
+ Border
+ by
+ Cancel
+ Cell margin
+ Choose
+ Close
+ Close Window
+ Comment
+ Confirm
+ Constrain proportions
+ Continue
+ Copy
+ Create
+ Database
+ Date
+ Default
+ Delete
+ Deleted
+ Deleting...
+ Design
+ Dimensions
+ Down
+ Download
+ Edit
+ Edited
+ Elements
+ Email
+ Error
+ Find
+ Height
+ Help
+ Icon
+ Import
+ Inner margin
+ Insert
+ Install
+ Invalid
+ Justify
+ Label
+ Language
+ Layout
+ Loading
+ Locked
+ Login
+ Log off
+ Logout
+ Macro
+ Mandatory
+ Move
+ More
+ Name
+ New
+ Next
+ No
+ of
+ OK
+ Open
+ or
+ Password
+ Path
+ Placeholder ID
+ One moment please...
+ Previous
+ Properties
+ Email to receive form data
+ Recycle Bin
+ Your recycle bin is empty
+ Remaining
+ Rename
+ Renew
+ Required
+ Retry
+ Permissions
+ Search
+ Sorry, we can not find what you are looking for
+ Server
+ Show
+ Show page on Send
+ Size
+ Sort
+ Submit
+ Type
+ Type to search...
+ Up
+ Update
+ Upgrade
+ Upload
+ Url
+ User
+ Username
+ Value
+ View
+ Welcome...
+ Width
+ Yes
+ Folder
+ Search results
+ Reorder
+ I am done reordering
+ Preview
+ Change password
+ to
+ List view
+ Saving...
+ current
+ Embed
+ selected
+
+
+ Black
+ Green
+ Yellow
+ Orange
+ Blue
+ Red
+
+
+ Add tab
+ Add property
+ Add editor
+ Add template
+ Add child node
+ Add child
+
+ Edit data type
+
+ Navigate sections
+
+ Shortcuts
+ show shortcuts
+
+ Toggle list view
+ Toggle allow as root
+
+
+ Background color
+ Bold
+ Text color
+ Font
+ Text
+
+
+ Page
+
+
+ The installer cannot connect to the database.
+ Could not save the web.config file. Please modify the connection string manually.
+ Your database has been found and is identified as
+ Database configuration
+ install button to install the Umbraco %0% database
+ ]]>
+ Next to proceed.]]>
+ Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.
+
To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.
]]>
+
+ Please contact your ISP if necessary.
+ If you're installing on a local machine or server you might need information from your system administrator.]]>
+
+ Press the upgrade button to upgrade your database to Umbraco %0%
+
+ Don't worry - no content will be deleted and everything will continue working afterwards!
+
+ ]]>
+
+ Press Next to
+ proceed. ]]>
+
+ next to continue the configuration wizard]]>
+ The Default users' password needs to be changed!]]>
+ The Default user has been disabled or has no access to Umbraco!
No further actions needs to be taken. Click Next to proceed.]]>
+ The Default user's password has been successfully changed since the installation!
No further actions needs to be taken. Click Next to proceed.]]>
+ The password is changed!
+
+ ('admin') and password ('default'). It's important that the password is changed to something unique.
+ ]]>
+ Get a great start, watch our introduction videos
+ By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI.
+ Not installed yet.
+ Affected files and folders
+ More information on setting up permissions for Umbraco here
+ You need to grant ASP.NET modify permissions to the following files/folders
+ Your permission settings are almost perfect!
+ You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
+ How to Resolve
+ Click here to read the text version
+ video tutorial on setting up folder permissions for Umbraco or read the text version.]]>
+ Your permission settings might be an issue!
+
+ You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
+ Your permission settings are not ready for Umbraco!
+
+ In order to run Umbraco, you'll need to update your permission settings.]]>
+ Your permission settings are perfect!
+ You are ready to run Umbraco and install packages!]]>
+ Resolving folder issue
+ Follow this link for more information on problems with ASP.NET and creating folders
+ Setting up folder permissions
+
+ I want to start from scratch
+
+ learn how)
+ You can still choose to install Runway later on. Please go to the Developer section and choose Packages.
+ ]]>
+ You've just set up a clean Umbraco platform. What do you want to do next?
+ Runway is installed
+
+ This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules
+ ]]>
+ Only recommended for experienced users
+ I want to start with a simple website
+
+
+ "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically,
+ but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However,
+ Runway offers an easy foundation based on best practices to get you started faster than ever.
+ If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.
+
+
+ Included with Runway: Home page, Getting Started page, Installing Modules page.
+ Optional Modules: Top Navigation, Sitemap, Contact, Gallery.
+
+ ]]>
+ What is Runway
+ Step 1/5 Accept license
+ Step 2/5: Database configuration
+ Step 3/5: Validating File Permissions
+ Step 4/5: Check Umbraco security
+ Step 5/5: Umbraco is ready to get you started
+ Thank you for choosing Umbraco
+ Browse your new site
+You installed Runway, so why not see how your new website looks.]]>
+ Further help and information
+Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]>
+ Umbraco %0% is installed and ready for use
+ /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]>
+ started instantly by clicking the "Launch Umbraco" button below. If you are new to Umbraco,
+you can find plenty of resources on our getting started pages.]]>
+ Launch Umbraco
+To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]>
+ Connection to database failed.
+ Umbraco Version 3
+ Umbraco Version 4
+ Watch
+ Umbraco %0% for a fresh install or upgrading from version 3.0.
+
Click here to reset your password or copy/paste this URL into your browser:
%1%
]]>
+
+
+
+ Dashboard
+ Sections
+ Content
+
+
+ Choose page above...
+ %0% has been copied to %1%
+ Select where the document %0% should be copied to below
+ %0% has been moved to %1%
+ Select where the document %0% should be moved to below
+ has been selected as the root of your new content, click 'ok' below.
+ No node selected yet, please select a node in the list above before clicking 'ok'
+ The current node is not allowed under the chosen node because of its type
+ The current node cannot be moved to one of its subpages
+ The current node cannot exist at the root
+ The action isn't allowed since you have insufficient permissions on 1 or more child documents.
+ Relate copied items to original
+
+
+ Edit your notification for %0%
+
+
+ Hi %0%
+
+
This is an automated mail to inform you that the task '%1%'
+ has been performed on the page '%2%'
+ by the user '%3%'
+
]]>
+ [%0%] Notification about %1% performed on %2%
+ Notifications
+
+
+
+ button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension.
+ ]]>
+ Author
+ Demonstration
+ Documentation
+ Package meta data
+ Package name
+ Package doesn't contain any items
+
+ You can safely remove this from the system by clicking "uninstall package" below.]]>
+ No upgrades available
+ Package options
+ Package readme
+ Package repository
+ Confirm uninstall
+ Package was uninstalled
+ The package was successfully uninstalled
+ Uninstall package
+
+ Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability,
+ so uninstall with caution. If in doubt, contact the package author.]]>
+ Download update from the repository
+ Upgrade package
+ Upgrade instructions
+ There's an upgrade available for this package. You can download it directly from the Umbraco package repository.
+ Package version
+ Package version history
+ View package website
+ Package already installed
+ This package cannot be installed, it requires a minimum Umbraco version of %0%
+ Uninstalling...
+ Downloading...
+ Importing...
+ Installing...
+ Restarting, please wait...
+ All done, your browser will now refresh, please wait...
+
+
+ Paste with full formatting (Not recommended)
+ The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.
+ Paste as raw text without any formatting at all
+ Paste, but remove formatting (Recommended)
+
+
+ Role based protection
+ using Umbraco's member groups.]]>
+ role-based authentication.]]>
+ Error Page
+ Used when people are logged on, but do not have access
+ Choose how to restrict access to this page
+ %0% is now protected
+ Protection removed from %0%
+ Login Page
+ Choose the page that contains the login form
+ Remove Protection
+ Select the pages that contain login form and error messages
+ Pick the roles who have access to this page
+ Set the login and password for this page
+ Single user protection
+ If you just want to setup simple protection using a single login and password
+
+
+
+
+
+
+
+
+
+ Include unpublished subpages
+ Publishing in progress - please wait...
+ %0% out of %1% pages have been published...
+ %0% has been published
+ %0% and subpages have been published
+ Publish %0% and all its subpages
+ Publish to publish %0% and thereby making its content publicly available.
+ You can publish this page and all its subpages by checking Include unpublished subpages below.
+ ]]>
+
+
+ You have not configured any approved colors
+
+
+ enter external link
+ choose internal page
+ Caption
+ Link
+ Open in new window
+ enter the display caption
+ Enter the link
+
+
+ Reset
+
+
+ Current version
+ Red text will not be shown in the selected version. , green means added]]>
+ Document has been rolled back
+ This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view
+ Rollback to
+ Select version
+ View
+
+
+ Edit script file
+
+
+ Concierge
+ Content
+ Courier
+ Developer
+ Umbraco Configuration Wizard
+ Media
+ Members
+ Newsletters
+ Settings
+ Statistics
+ Translation
+ Users
+ Help
+ Forms
+ Analytics
+
+
+ go to
+ Help topics for
+ Video chapters for
+ The best Umbraco video tutorials
+
+
+ Default template
+ Dictionary Key
+ To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen)
+ New Tab Title
+ Node type
+ Type
+ Stylesheet
+ Script
+ Stylesheet property
+ Tab
+ Tab Title
+ Tabs
+ Master Content Type enabled
+ This Content Type uses
+ as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself
+ No properties defined on this tab. Click on the "add a new property" link at the top to create a new property.
+ Master Document Type
+ Create matching template
+ Add icon
+
+
+ Sort order
+ Creation date
+ Sorting complete.
+ Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items
+ Do not close this window during sorting]]>
+
+
+ Validation
+ Validation errors must be fixed before the item can be saved
+ Failed
+ Insufficient user permissions, could not complete the operation
+ Cancelled
+ Operation was cancelled by a 3rd party add-in
+ Publishing was cancelled by a 3rd party add-in
+ Property type already exists
+ Property type created
+ DataType: %1%]]>
+ Propertytype deleted
+ Document Type saved
+ Tab created
+ Tab deleted
+ Tab with id: %0% deleted
+ Stylesheet not saved
+ Stylesheet saved
+ Stylesheet saved without any errors
+ Datatype saved
+ Dictionary item saved
+ Publishing failed because the parent page isn't published
+ Content published
+ and visible at the website
+ Content saved
+ Remember to publish to make changes visible
+ Sent For Approval
+ Changes have been sent for approval
+ Media saved
+ Media saved without any errors
+ Member saved
+ Stylesheet Property Saved
+ Stylesheet saved
+ Template saved
+ Error saving user (check log)
+ User Saved
+ User type saved
+ File not saved
+ file could not be saved. Please check file permissions
+ File saved
+ File saved without any errors
+ Language saved
+ Media Type saved
+ Member Type saved
+ Python script not saved
+ Python script could not be saved due to error
+ Python script saved
+ No errors in python script
+ Template not saved
+ Please make sure that you do not have 2 templates with the same alias
+ Template saved
+ Template saved without any errors!
+ XSLT not saved
+ XSLT contained an error
+ XSLT could not be saved, check file permissions
+ XSLT saved
+ No errors in XSLT
+ Content unpublished
+ Partial view saved
+ Partial view saved without any errors!
+ Partial view not saved
+ An error occurred saving the file.
+ Script view saved
+ Script view saved without any errors!
+ Script view not saved
+ An error occurred saving the file.
+ An error occurred saving the file.
+
+
+ Uses CSS syntax ex: h1, .redHeader, .blueTex
+ Edit stylesheet
+ Edit stylesheet property
+ Name to identify the style property in the rich text editor
+ Preview
+ Styles
+
+
+ Edit template
+ Insert content area
+ Insert content area placeholder
+ Insert dictionary item
+ Insert Macro
+ Insert Umbraco page field
+ Master template
+ Quick Guide to Umbraco template tags
+ Template
+
+
+ Choose type of content
+ Choose a layout
+ Add a row
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Click to embed
+ Click to insert image
+ Image caption...
+ Write here...
+
+ Grid Layouts
+ Layouts are the overall work area for the grid editor, usually you only need one or two different layouts
+ Add Grid Layout
+ Adjust the layout by setting column widths and adding additional sections
+ Row configurations
+ Rows are predefined cells arranged horizontally
+ Add row configuration
+ Adjust the row by setting cell widths and adding additional cells
+
+ Columns
+ Total combined number of columns in the grid layout
+
+ Settings
+ Configure what settings editors can change
+
+
+ Styles
+ Configure what styling editors can change
+
+ Settings will only save if the entered json configuration is valid
+
+ Allow all editors
+ Allow all row configurations
+ Set as default
+ Choose extra
+ Choose default
+ are added
+
+
+
+ Compositions
+ You have not added any tabs
+ Add new tab
+ Add another tab
+ Inherited from
+ Add property
+ Required label
+
+ Enable list view
+ Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree
+
+ Allowed Templates
+ Choose which templates editors are allowed to use on content of this type
+ Allow as root
+ Allow editors to create content of this type in the root of the content tree
+ Yes - allow content of this type in the root
+
+ Allowed child node types
+ Allow content of the specified types to be created underneath content of this type
+
+ Choose child node
+ Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists.
+ This content type is used in a composition, and therefore cannot be composed itself.
+ There are no content types available to use as a composition.
+
+ Available editors
+ Reuse
+ Editor settings
+
+ Configuration
+
+ Yes, delete
+
+ was moved underneath
+ was copied underneath
+ Select the folder to move
+ Select the folder to copy
+ to in the tree structure below
+
+ All Document types
+ All Documents
+ All media items
+
+ using this document type will be deleted permanently, please confirm you want to delete these as well.
+ using this media type will be deleted permanently, please confirm you want to delete these as well.
+ using this member type will be deleted permanently, please confirm you want to delete these as well
+
+ and all documents using this type
+ and all media items using this type
+ and all members using this type
+
+ using this editor will get updated with the new settings
+
+ Member can edit
+ Show on member profile
+ tab has no sort order
+
+
+
+ Building models
+ this can take abit of time, don't worry
+ Models generated
+ Models could not be generated
+ Models generation has failed, see exception in U log
+
+
+
+ Alternative field
+ Alternative Text
+ Casing
+ Encoding
+ Choose field
+ Convert line breaks
+ Replaces line breaks with html-tag <br>
+ Custom Fields
+ Yes, Date only
+ Format as date
+ HTML encode
+ Will replace special characters by their HTML equivalent.
+ Will be inserted after the field value
+ Will be inserted before the field value
+ Lowercase
+ None
+ Insert after field
+ Insert before field
+ Recursive
+ Remove Paragraph tags
+ Will remove any <P> in the beginning and end of the text
+ Standard Fields
+ Uppercase
+ URL encode
+ Will format special characters in URLs
+ Will only be used when the field values above are empty
+ This field will only be used if the primary field is empty
+ Yes, with time. Separator:
+
+
+ Tasks assigned to you
+ assigned to you. To see a detailed view including comments, click on "Details" or just the page name.
+ You can also download the page as XML directly by clicking the "Download Xml" link.
+ To close a translation task, please go to the Details view and click the "Close" button.
+ ]]>
+ close task
+ Translation details
+ Download all translation tasks as XML
+ Download XML
+ Download XML DTD
+ Fields
+ Include subpages
+
+ [%0%] Translation task for %1%
+ No translator users found. Please create a translator user before you start sending content to translation
+ Tasks created by you
+ created by you. To see a detailed view including comments,
+ click on "Details" or just the page name. You can also download the page as XML directly by clicking the "Download Xml" link.
+ To close a translation task, please go to the Details view and click the "Close" button.
+ ]]>
+ The page '%0%' has been send to translation
+ Please select the language that the content should be translated into
+ Send the page '%0%' to translation
+ Assigned by
+ Task opened
+ Total words
+ Translate to
+ Translation completed.
+ You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages.
+ Translation failed, the XML file might be corrupt
+ Translation options
+ Translator
+ Upload translation XML
+
+
+ Cache Browser
+ Recycle Bin
+ Created packages
+ Data Types
+ Dictionary
+ Installed packages
+ Install skin
+ Install starter kit
+ Languages
+ Install local package
+ Macros
+ Media Types
+ Members
+ Member Groups
+ Member Roles
+ Member Types
+ Document Types
+ Relation Types
+ Packages
+ Packages
+ Python Files
+ Install from repository
+ Install Runway
+ Runway modules
+ Scripting Files
+ Scripts
+ Stylesheets
+ Templates
+ XSLT Files
+ Analytics
+
+
+ New update ready
+ %0% is ready, click here for download
+ No connection to server
+ Error checking for update. Please review trace-stack for further information
+
+
+ Administrator
+ Category field
+ Change Your Password
+ New password
+ Confirm new password
+ You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button
+ Content Channel
+ Description field
+ Disable User
+ Document Type
+ Editor
+ Excerpt field
+ Language
+ Login
+ Start Node in Media Library
+ Sections
+ Disable Umbraco Access
+ Old password
+ Password
+ Reset password
+ Your password has been changed!
+ Please confirm the new password
+ Enter your new password
+ Your new password cannot be blank!
+ Current password
+ Invalid current password
+ There was a difference between the new password and the confirmed password. Please try again!
+ The confirmed password doesn't match the new password!
+ Replace child node permissions
+ You are currently modifying permissions for the pages:
+ Select pages to modify their permissions
+ Search all children
+ Start Node in Content
+ Name
+ User permissions
+ User type
+ User types
+ Writer
+ Translator
+ Change
+ Your profile
+ Your recent history
+ Session expires in
+
+
+ Validation
+ Validate as email
+ Validate as a number
+ Validate as a Url
+ ...or enter a custom validation
+ Field is mandatory
+
+
+
+ Value is set to the recommended value: '%0%'.
+ Value was set to '%1%' for XPath '%2%' in configuration file '%3%'.
+ Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'.
+ Found unexpected value '%0%' for '%2%' in configuration file '%3%'.
+
+
+ Custom errors are set to '%0%'.
+ Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live.
+ Custom errors successfully set to '%0%'.
+
+ MacroErrors are set to '%0%'.
+ MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely when there's any errors in macros. Rectifying this will set the value to '%1%'.
+ MacroErrors are now set to '%0%'.
+
+
+ Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'.
+ Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%).
+ Try Skip IIS Custom Errors successfully set to '%0%'.
+
+
+ File does not exist: '%0%'.
+ '%0%' in config file '%1%'.]]>
+ There was an error, check log for full error: %0%.
+
+ Members - Total XML: %0%, Total: %1%, Total invalid: %2%
+ Media - Total XML: %0%, Total: %1%, Total invalid: %2%
+ Content - Total XML: %0%, Total published: %1%, Total invalid: %2%
+
+ Your site certificate was marked as valid.
+ Certificate validation error: '%0%'
+ Error pinging the URL %0% - '%1%'
+ You are currently %0% viewing the site using the HTTPS scheme.
+ The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'.
+ The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure.
+ Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0%
+
+
+ Enable HTTPS
+ Sets umbracoSSL setting to true in the appSettings of the web.config file.
+ The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure.
+
+ Fix
+ Cannot fix a check with a value comparison type of 'ShouldNotEqual'.
+ Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value.
+ Value to fix check not provided.
+
+ Debug compilation mode is disabled.
+ Debug compilation mode is currently enabled. It is recommended to disable this setting before go live.
+ Debug compilation mode successfully disabled.
+
+ Trace mode is disabled.
+ Trace mode is currently enabled. It is recommended to disable this setting before go live.
+ Trace mode successfully disabled.
+
+ All folders have the correct permissions set.
+
+ %0%.]]>
+ %0%. If they aren't being written to no action need be taken.]]>
+
+ All files have the correct permissions set.
+
+ %0%.]]>
+ %0%. If they aren't being written to no action need be taken.]]>
+
+ X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]>
+ X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]>
+ Set Header in Config
+ Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites.
+ A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file.
+ Could not update web.config file. Error: %0%
+
+
+ %0%.]]>
+ No headers revealing information about the website technology were found.
+
+ In the Web.config file, system.net/mailsettings could not be found.
+ In the Web.config file system.net/mailsettings section, the host is not configured.
+ SMTP settings are configured correctly and the service is operating as expected.
+ The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct.
+
+ %0%.]]>
+ %0%.]]>
+
+
+ Disable URL tracker
+ Enable URL tracker
+ Original URL
+ Redirected To
+ No redirects have been made
+ When a published page gets renamed or moved a redirect will automatically be made to the new page.
+ Remove
+ Are you sure you want to remove the redirect from '%0%' to '%1%'?
+ Redirect URL removed.
+ Error removing redirect URL.
+ Are you sure you want to disable the URL tracker?
+ URL tracker has now been disabled.
+ Error disabling the URL tracker, more information can be found in your log file.
+ URL tracker has now been enabled.
+ Error enabling the URL tracker, more information can be found in your log file.
+
+
diff --git a/WebCms/Umbraco/Config/Lang/es.xml b/WebCms/Umbraco/Config/Lang/es.xml
new file mode 100644
index 0000000..4d65235
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/es.xml
@@ -0,0 +1,936 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Administrar hostnames
+ Auditoría
+ Nodo de Exploración
+ Cambiar tipo de documento
+ Copiar
+ Crear
+ Crear Paquete
+ Borrar
+ Deshabilitar
+ Vaciar Papelera
+ Exportar Documento (tipo)
+ Importar Documento (tipo)
+ Importar Paquete
+ Editar en lienzo
+ Salir
+ Mover
+ Notificaciones
+ Acceso Público
+ Publicar
+ Unpublish
+ Recargar Nodos
+ Republicar sitio completo
+ Permisos
+ Deshacer
+ Enviar a Publicar
+ Enviar a Traducir
+ Ordernar
+ Enviar a publicación
+ Traducir
+ Actualizar
+ Valor por defecto
+
+
+ Permiso denegado.
+ Añadir nuevo dominio
+ quitar
+ Nodo no válido.
+ Formato de dominio no válido.
+ Este dominio ya ha sido asignado.
+ Language
+ Dominio
+ El nuevo dominio %0% ha sido creado
+ El dominio %0% ha sido borrado
+ El dominio'%0%' ya ha sido asignado
+ El dominio %0% ha sido actualizado
+ Editar dominios actuales
+
+ Los dominios de un nivel están soportados, por ej. "example.com/en". De todas formas deberían de evitarse. Mejor usar la configuración cultural especificada arriba.]]>
+
+ Heredar
+ Cultura
+
+ o hereda la cultura de los nodos padres. También se aplicará
+ para el nodo actual, a menos que un dominio por debajo lo aplique también.]]>
+
+ Domains
+
+
+ Visualización de
+
+
+ Seleccionar
+ Selecciona la carpeta actual
+ Hacer otra cosa
+ Negrita
+ Cancelar Sangría del Párrafo
+ Insertar campo de formulario
+ Insertar gráfico de titular
+ Editar Html
+ Sangría
+ Cursiva
+ Centrar
+ Alinear a la Izquierda
+ Alinear a la Derecha
+ Insertar Link
+ Insertar link local (anchor)
+ Lista en Viñetas
+ Lista Numérica
+ Insertar macro
+ Insertar imagen
+ Editar relaciones
+ Guardar
+ Guardar y publicar
+ Guardar y enviar para aprobación
+ Previsualizar
+ La previsualización está deshabilitada porque no hay ninguna plantilla asignada
+ Elegir estilo
+ Mostrar estilos
+ Insertar tabla
+ Volver al listado
+
+
+ Para cambiar el tipo de documento al contenido seleccionado, primero selecciona uno de la lista de tipos válidos.
+ Entonces confirma el mapeo de propiedades del tipo actual al nuevo y haz click en Guardar.
+ El contenido se ha vuelto a publicar.
+ Propiedad actual
+ Tipo actual
+ El tipo de contenido no se puede cambiar, porque no hay alternativas válidas para este contenido.
+ Tipo de documento cambiado
+ Mapeo de propiedades
+ Mapea a la propiedad
+ Nueva plantilla
+ Nuevo tipo
+ ninguno
+ Contenido
+ Selecciona un nuevo Tipo de Documento
+ El tipo de documento del contenido seleccionado ha sido cambiado correctamente a [new type] y las siguientes propiedades mapeadas:
+ a
+ No se ha podido completar el mapeo de propiedades porque uno o más propiedades tienen más de un mapeo definido.
+ Solo se muestran otros tipos válidos para el contenido actual.
+
+
+ Está publicado
+ Acerca de
+ Link alternativo
+ (como describe la imagen sobre el teléfono)
+ Vinculos Alternativos
+ Click para editar esta entrada
+ Creado por
+ Autor original
+ Actualizado por
+ Creado
+ Fecha/hora de creación del documento
+ Tipo de Documento
+ Editando
+ Remover el
+ Esta entrada ha sido modificada después de haber sido publicada
+ Esta entrada no esta publicada
+ Último publicado
+ Tipo de Medio
+ Miembro de Grupo
+ Rol
+ Tipo de miembro
+ Sin fecha
+ Título de la página
+ Propiedades
+ Este documento ha sido publicado pero no es visible porque el padre '%0%' no esta publicado
+ Upss: este documento está publicado pero no está en la caché (error interno)
+ Publicar
+ Estado de la Publicación
+ Publicar el
+ Despublicar el
+ Fecha de Eliminación
+ El Orden esta actualizado
+ Para organizar los nodos, simplemente arrastre los nodos o realice un clic en uno de los encabezados de columna. Puede seleccionar multiple nodos manteniendo presionados "Shift" o "Control" mientras selecciona
+ Estadísticas
+ Título (opcional)
+ Tipo
+ No Publicar
+ Última actualización
+ Fecha/hora este documento fue modificado
+ Eliminar archivo
+ Vínculo al documento
+ Miembro de grupo(s)
+ No es miembreo de grupo(s)
+ Nodos hijo
+ Target
+ No hay datos que mostrar
+
+
+ Haz click para subir archivos
+ Arrastra los archivos aquí...
+
+
+ ¿Dónde quieres crear el nuevo %0%
+ Crear debajo de
+ Elije un tipo y un título
+ "Tipos de documentos".]]>
+ "Tipos de medios".]]>
+
+
+ Navega en tu sitio Web
+ No volver a mostrar
+ Si Umbraco no se ha abierto tendrás que permitir ventanas emergentes para este sitio Web
+ se ha abierto en una nueva ventana
+ Reinicio
+ Visita
+ Bienvenido
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Nombre
+ Administrar dominios
+ Cerrar esta ventana
+ Esta usted seguro que desea borrar
+ Esta usted seguro que desea deshabilitar
+ Por favor seleccione esta casilla para confirmar la eliminación de %0% entrada(s)
+ Esta usted seguro?
+ Esta usted Seguro?
+ Cortar
+ Editar entrada del Diccionario
+ Editar idioma
+ Agregar enlace interno
+ Insertar caracter
+ Insertar titular gráfico
+ Insertar imagen
+ Insertar enlace
+ Insertar macro
+ Insertar tabla
+ Última edición
+ Enlace
+ Enlace interno
+ Al usar enlaces locales, insertar "#" delante del enlace
+ ¿Abrir en nueva ventana?
+ Ajustes para la Macro
+ Esta macro no contiene ninguna propiedad que pueda editar
+ Pegar
+ Editar permisos para
+ Se está vaciando la papelera. No cierre esta ventana mientras se ejecuta este proceso
+ La papelera está vacía
+ No podrá recuperar los items una vez sean borrados de la papelera
+ regexlib.com está experimentando algunos problemas en estos momentos, de los cuales no somos responsables. Pedimos disculpas por las molestias.]]>
+ Buscar una expresión regular para agregar validación a un campo de formulario. Ejemplo: 'correo electrónico', código postal "," url "
+ Eliminar macro
+ Campo obligatorio
+ El sitio ha sido reindexado
+ Se ha actualizado la caché y se ha publicado el contenido del sitio web.
+ La caché del sitio web será actualizada. Todos los contenidos publicados serán actualizados, mientras el contenido no publicado permanecerá no publicado.
+ Número de columnas
+ Número de filas
+ Coloca un 'placeholder' id al colocar un ID en tu 'placeholder' puedes insertar contenido en esta plantilla desde una plantilla hija, referenciando este ID usando un elemento<asp:content />.]]>
+ Seleccione una tecla de la lista abajo indicada. Sólo puede elegir a partir de la ID de la plantilla actual del dominio.
+ Haga clic sobre la imagen para verla a tamaño completo.
+ Seleccionar item
+ Ver item en la caché
+
+
+ Editar las diferentes versiones lingüísticas para la entrada en el diccionario '% 0%' debajo añadir otros idiomas en el menu de 'idiomas' en el menú de la izquierda
+
+
+
+ Escribe tu nombre de usuario
+ Escribe tu contraseña
+ Nombre del %0%...
+ Escribe un nombre...
+ Escribe tu búsqueda...
+ Escribe para filtrar resultados...
+
+
+ Permitir en nodo raíz
+ Sólo tipos de contenido permitidos podrán crearse bajo el nodo raíz de los árboles de Contenido y Media
+ Tipos de nodos hijos permitidos
+ Composiciones de Tipo de Documento
+ Crear
+ Borrar pestaña
+ Descripción
+ Nueva pestaña
+ Pestaña
+ Miniatura
+ Permitir vista de listado
+ Configura el contenido para mostrar un listado de nodos hijos, en lugar de mostrarlos en forma de árbol
+ Vista de listado actual
+ El tipo de vista de listado activa
+ Crear un tipo de listado personalizado
+ Quitar el tipo de listado personalizado
+
+
+ añadir prevalor
+
+ Tipo de datos GUID
+ Tipo de datos GUIDprestar control
+ Botones
+ Habilitar la configuración avanzada para
+ Habilitar menú contextual
+ Por defecto, el tamaño máximo de imágenes insertado
+
+ Mostrar etiqueta
+
+
+
+ Se ha guardado la información pero debes solucionar los siguientes errores para poder publicar:
+ La composición actual del proveedor no es compatible con el cambio de la contraseña (Habilitar la contraseña de recuperación es necesaria para que sea cierta)
+ %0% ya existe
+ Se han encontrado los siguientes errores:
+ Se han encontrado los siguientes errores:
+ La clave debe tener como mínimo %0% caracteres y %1% caracter(es) no alfanuméricos
+ %0% debe ser un número entero
+ Debe llenar los campos del %0% al %1%
+ Debe llenar el campo %0%
+ Debe poner el formato correcto del %0% al %1%
+ Debe poner un formato correcto en %0%
+
+
+ NOTA: Aunque CodeMirror esté activado en los ajustes de configuracion, no se muestra en Internet Explorer debido a que no es lo suficientemente estable.'
+ Debe llenar el alias y el nombre en el propertytype
+ Hay un problema de lectura y escritura al acceder a un archivo o carpeta
+
+ Por favor, elija un tipo
+ Usted está a punto de hacer la foto más grande que el tamaño original. ¿Está seguro de que desea continuar?
+ Error en script python
+ El script python no se ha guardado debido a que contenía error(es)
+
+ Por favor, marque el contenido antes de cambiar de estilo
+ No active estilos disponibles
+
+
+
+ El XSLT no se ha guardado, porque contenía un error (s)
+
+
+ Acerca de
+ Acción
+ Acciones
+ Añadir
+ Alias
+ ¿Está seguro?
+ Borde
+ o
+ Cancelar
+ Margen de la celda
+ Elegir
+ Cerrar
+ Cerrar ventana
+ Comentario
+ Confirmar
+ Mantener proporciones
+ Continuar
+ Copiar
+ Crear
+ Base de datos
+ Fecha
+ Por defecto
+ Borrar
+ Borrado
+ Borrando...
+ Diseño
+ Dimensiones
+ Abajo
+ Descargar
+ Editar
+ Editado
+ Elementos
+ Mail
+ Error
+ Buscar
+ Altura
+ Ayuda
+ Icono
+ Importar
+ Margen interno
+ Insertar
+ Instalar
+ Justificar
+ Idioma
+ Diseño
+ Cargando
+ Bloqueado
+ Iniciar sesión
+ Cerrar sesión
+ Cerrar sesión
+ Macro
+ Mover
+ Nombre
+ New
+ Próximo
+ No
+ de
+ OK
+ Abrir
+ o
+ Contraseña
+ Ruta
+ ID de marcador de posición
+ Un momento por favor...
+ Anterior
+ Propiedades
+ Mail para recibir los datos del formulario
+ Papelera
+ Restantes
+ Renombrar
+ Renovar
+ Reintentar
+ Permisos
+ Buscar
+ Servidor
+ Mostrar
+ Mostrar página al enviar
+ Tamaño
+ Ordenar
+ Submit
+ Tipo
+ Tipo que buscar...
+ Arriba
+ Actualizar
+ Actualizar
+ Upload
+ Url
+ Usuario
+ Nombre de usuario
+ Valor
+ Ver
+ Bienvenido...
+ Ancho
+ Si
+ Reorder
+ I am done reordering
+
+
+ Color de fondo
+ Negritas
+ Color del texto
+ Fuente
+ Texto
+
+
+ Página
+
+
+ El instalador no puede conectar con la base de datos.
+ No se ha podido guardar el archivo Web.config. Por favor, modifique la cadena de conexión manualmente.
+ Su base de datos ha sido encontrada y ha sido identificada como
+ Configuración de la base de datos
+ instalar strong> para instalar %0% la base de datos de Umbraco]]>
+ Próximo para continuar]]>
+ ¡No se ha encontrado ninguna base de datos! Mira si la información en la "connection string" del “web.config” es correcta.
Para continuar, edite el "web.config" (bien sea usando Visual Studio o su editor de texto preferido), vaya al final del archivo y añada la cadena de conexión para la base de datos con el nombre (key) "umbracoDbDSN" y guarde el archivo.
]]>
+ Por favor, contacta con tu ISP si es necesario. Si estás realizando la instalación en una máquina o servidor local, quizás necesites información de tu administrador de sistemas.]]>
+ Pinche en actualizar para actualizar la base de datos a Umbraco %0%
Ningún contenido será borrado de la base de datos y seguirá funcionando después de la actualización
]]>
+ Pinche en Próximo para continuar. ]]>
+ próximo para continuar con el asistente de configuración]]>
+ La contraseña del usuario por defecto debe ser cambiada]]>
+ El usuario por defecto ha sido desabilitado o ha perdido el acceso a Umbraco!
Pinche en Próximo para continuar.]]>
+ ¡La contraseña del usuario por defecto ha sido cambiada desde que se instaló!
No hay que realizar ninguna tarea más. Pulsa Siguiente para proseguir.]]>
+ ¡La constraseña se ha cambiado!
+ Umbraco crea un usuario por defecto con un nombre de usuario ('admin') y constraseña ('default'). Es importante que la contraseña se cambie a algo único.
Este paso comprobará la contraseña del usuario por defecto y sugerirá si debe cambiarse.
]]>
+ Ten un buen comienzo, visita nuestros videos de introducción
+ Pulsando el botón de Siguiente (o modificando el UmbracoConfigurationStatus en el web.config), aceptar la licencia de este software tal y como se especifica en el cuadro de debajo. Ten en cuenta que esta distribución de Umbraco consta de dos licencias diferentes, la licencia open source MIT para el framework y la licencia Umbraco freeware que cubre la IU.
+ No ha sido instalado.
+ Archivos y directorios afectados
+ Mas información en configurar los permisos para Umbraco aquí
+ Necesitas dar permisos de modificación a ASP.NET para los siguientes archivos/directorios
+ ¡Tu configuración de permisos es casi perfecta!
Puedes ejecutar Umbraco sin problemas, pero no podrás instalar paquetes que es algo recomendable para explotar el potencial de Umbraco.]]>
+ Como Resolver
+ Pulsa aquí para leer la versión de texto
+ video tutoriales acerca de cómo configurar los permisos de los directorios para Umbraco o lee la versión de texto.]]>
+ ¡La configuración de tus permisos podría ser un problema!
Puedes ejecutar Umbraco sin problemas, pero no serás capaz de crear directorios o instalar paquetes que es algo recomendable para explotar el potencial de Umbraco.]]>
+ ¡Tu configuración de permisos no está lista para Umbraco!
Para ejecutar Umbraco, necesitarás actualizar tu configuración de permisos.]]>
+ ¡Tu configuración de permisos es perfecta!
¡Estás listo para ejecutar Umbraco e instalar paquetes!]]>
+ Resolviendo problemas con directorios
+ Sigue este enlace para más información sobre problemas con ASP.NET y creación de directorios
+ Configurando los permisos de directorios
+ Umbraco necesita permisos de lectura/escritura en algunos directorios para poder almacenar archivos tales como imagenes y PDFs. También almacena datos en la caché para mejorar el rendimiento de su sitio web
+ Quiero empezar de cero
+ learn how). Todavía podrás elegir instalar Runway más adelante. Por favor ve a la sección del Desarrollador y elije Paquetes.]]>
+ Acabas de configurar una nueva plataforma Umbraco. ¿Qué deseas hacer ahora?
+ Se ha instalado Runway
+ Esta es nuestra lista de módulos recomendados, selecciona los que desees instalar, o mira la lista completa de módulos ]]>
+ Sólo recomendado para usuarios expertos
+ Quiero empezar con un sitio web sencillo
+ "Runway" es un sitio web sencillo que contiene unos tipos de documentos y plantillas básicos. El instalador puede configurar Runway por ti de forma automática, pero fácilmente puedes editarlo, extenderlo o eliminarlo. No es necesario y puedes usar Umbrao perfectamente sin él. Sin embargo, Runway ofrece unos cimientos sencillos basados en buenas prácticas para iniciarte más rápido que nunca. Si eliges instalar Runway, puedes seleccionar bloques de construcción básicos llamados Módulos de Runway de forma opcional para realzar tus páginas de Runway. > Incluido con Runway: Página de inicio, página de Cómo empezar, página de Instalación de módulos. Módulos opcionales: Navegación superior, Mapa del sitio, Contacto, Galería. ]]>
+ ¿Qué es Runway?
+ Paso 1 de 5. Aceptar los términos de la licencia
+ Paso 2 de 5. Configuración de la base de datos
+ Paso 3 de 5. Autorizar / validar permiso en los archivos
+ Paso 4 de 5. Configurar seguridad en Umbraco
+ Paso 5 de 5. Umbraco está listo para ser usado
+ Gracias por elegir Umbraco
+ Navega a tu nuevo sitio Has instalado Runway, por qué no ves el aspecto de tu nuevo sitio web.]]>
+ Más ayuda e información Consigue ayuda de nuestra premiada comunidad, navega por la documentación o mira algunos videos gratuitos de cómo crear un sitio sencillo, cómo utilizar los paquetes y una guía rápida de la terminología de Umbraco]]>
+ Umbraco %0% ha sido instalado y está listo para ser usado
+ archivo /web.config y actualizar la clave del AppSetting UmbracoConfigurationStatus del final al valor '%0%'.]]>
+ empezar inmediatamente pulsando el botón "Lanzar Umbraco" de debajo. Si eres nuevo con Umbraco, puedes encontrar cantidad de recursos en nuestras páginas de cómo empezar.]]>
+ Lanzar Umbraco Para administrar tu sitio web, simplemente abre el back office de Umbraco y empieza a añadir contenido, a actualizar plantillas y hojas de estilo o a añadir nueva funcionalidad]]>
+ No se ha podido establecer la conexión con la base de datos
+ Umbraco versión 3
+ Umbraco versión 4
+ Mirar
+ Umbraco %0% o actualizar la versión 3.0 a Umbraco %0%.
]]>
+ [%0%] Notificación acerca de %1% realizado en %2%
+ Notificaciones
+
+
+ y localizando el paquete. Los paquetes de Umbraco normalmente tienen la extensión ".umb" o ".zip".]]>
+ Autor
+
+ Documentación
+ Meta datos del paquete
+ Nombre del paquete
+ El paquete no contiene ningún elemento
+ Puedes eliminarlo del sistema de forma segura seleccionando la opción "desinstalar paquete" de abajo.]]>
+ No hay actualizaciones disponibles
+ Opciones del paquete
+ Leeme del paquete
+ Repositorio de paquetes
+ Confirma la desinstalación
+ El paquete ha sido desinstalado
+ El paquete se ha desinstalado correctamente
+ Desinstalar paquete
+ Nota: cualquier documento, archivo etc dependiente de los elementos eliminados, dejará de funcionar, y puede conllevar inestabilidad en el sistema, por lo que lleva cuidado al desinstalar elementos. En caso de duda, contacta con el autor del paquete.]]>
+ Descargar actualización del repositorio
+ Actualizar paquete
+ Instrucciones de actualización
+ Hay una actualización disponible para este paquete. Puedes descargarla directamente del repositorio de paquetes de Umbraco.
+ Versión del paquete
+ Ver página web del paquete
+
+
+ Pegar con formato completo (No recomendado)
+ El texto que estás intentando pegar contiene caractéres o formato especial. El problema puede ser debido al copiar texto desde Microsoft Word. Umbraco puede eliminar estos caractéres o formato especial automáticamente, de esa manera el contenido será más adecuado para la web.
+ Pegar como texto sin formato
+ Pegar, pero quitando el formato (Recomendado)
+
+
+ Proteccion basada en roles
+ usando los grupos de miembros de Umbraco.]]>
+ autenticación basada en roles.]]>
+ Página de error
+ Usada cuando alguien hace login, pero no tiene acceso
+ Elija cómo restringir el acceso a esta página
+ %0% está protegido
+ Protección borrada de %0%
+ Página de login
+ Elija la página que contenga el formulario de login
+ Borrar protección
+ Elija las páginas que contendrán el formulario de login y mensajes de error
+ Elija los roles que tendrán acceso a esta página
+ Elija el login y password para esta página
+ Protección de usuario único
+ Si sólo necesita configurar una protección simple usando un único login y password
+
+
+ %0% no ha podido ser publicado, debido a que una extensión de otro proveedor ha cancelado la acción.
+ Incluir las páginas hija sin publicar
+ Publicación en progreso - por favor, espera...
+ Se han publicado %0% de %1% páginas...
+ %0% se ha publicado
+ %0% y sus subpáginas se han publicado
+ Publicar %0% y todas sus subpáginas
+ aceptar para publicar %0% y por lo tanto, hacer que su contenido esté disponible al público.
Puedes publicar esta página y todas sus subpáginas marcando publicar todos los hijos debajo. ]]>
+
+
+ Añadir un enlace externo
+ Añadir un enlace interno
+ Añadir
+ Título
+ Página interna
+ Enlace
+ Bajar
+ Subir
+ Abrir en una nueva ventana
+ Quitar el enlace
+
+
+ Versión actual
+ Red el texto de la versión seleccionada no se mostrará. , green means added]]>
+ Se ha recuperado la última versión del documento.
+ Esto muestra la versión seleccionada como html, si desea ver la diferencia entre 2 versiones al mismo tiempo, por favor use la vista diff
+ Volver a
+ Elija versión
+ Vista
+
+
+ Editar fichero de script
+
+
+ Conserje
+ Contenido
+ Mensajero
+ Desarrollador
+ Asistente de configuración de Umbraco
+ Media
+ Miembros
+ Boletín informativo
+ Ajustes
+ Estadísticas
+ Traducción
+ Usuarios
+ Ayuda
+ Analytics
+
+
+ Plantilla por defecto
+ Clave de diccionario
+ Para importar un tipo de documento encuentre el fichero ".udt" en su ordenador haciendo click sobre el botón "Navegar" y pulsando "Importar" (se le solicitará confirmación en la siguiente pantalla)
+ Nuevo nombre de la pestaña
+ Tipo de nodo
+ Tipo
+ Hoja de estilos
+ Propiedades de la hoja de estilos
+ Pestaña
+ Nombre de la pestaña
+ Pestañas
+ Tipo de Contenido Maestro activado
+ Este Tipo de Contenido usa
+ como Tipo de Contenido Maestro. Las pestañas para los Tipos de Contenido Maestros no se muestran y solo se pueden modificar desde el Tipo de Contenido Maestro
+ No existen propiedades para esta pestaña. Haga clic en el enlace "añadir nueva propiedad" para crear una nueva propiedad.
+ Tipo de documento Maestro
+ Crear template correspondiente
+
+
+ Sort order
+ Creation date
+ Ordenación completa.
+ Arrastra las diferentes páginas debajo para colocarlas como deberían estar. O haz click en las cabeceras de las columnas para ordenar todas las páginas
+
+ No cierre esta ventana mientras se está ordenando ]]>
+
+
+ La publicación fue cancelada por un complemento de terceros
+ El tipo de propiedad ya existe
+ Tipo de propiedad creado
+ Tipo de Dato: %1%]]>
+ Tipo de propiedad eliminado
+ Tipo de contenido guardado
+ Pestaña creada
+ Pestaña eliminada
+ Pestaña con id: %0% eliminada
+ La hoja de estilos no se ha guardado
+ Hoja de estilos guardada
+ La hoja de estilos se ha guardado sin errores
+ Tipo de dato guardado
+ Elemento del diccionario guardado
+ La publicación ha fallado porque la página padre no está publicada
+ Contenido publicado
+ y visible en el sitio web
+ Contenido guardado
+ Recuerda publicar para hacer los cambios visibles
+ Mandado para ser aprobado
+ Los cambios se han mandado para ser aprobados
+ Miembro guardado
+ Propiedad de la hoja de estilos guardada
+ Hoja de estilos guardada
+ Plantilla guardada
+ Error grabando usuario (comprueba el log)
+ Usuario grabado
+ El archivo no se ha guardado
+ El archivo no se ha grabado. Por favor, comprueba los permisos de los ficheros
+ Archivo guardado
+ Archivo grabado sin errores
+ Lenguaje guardado
+ El script en Python no se ha guardado
+ El script en Python no se ha podido guardar debido a un error
+ Script en Python guardado
+ No hay errores en el script en Python
+ La plantilla no se ha guardado
+ Por favor, asegúrate de que no hay 2 plantillas con el mismo alias
+ Plantilla guardada
+ Plantilla guardada sin errores
+ El XSLT no se ha guardado
+ El XSLT tenía un error
+ El XSLT no se ha podido guardar, comprueba los permisos de los ficheros
+ XSLT guardado
+ No hay errores en el XSLT
+
+
+ Usa sintaxis CSS, p.ej.: h1, .redHeader, .blueTex
+ Editar hoja de estilos
+ Editar propiedades de la hoja de estilos
+ Nombre para identificar la propiedad del estilo en el editor de texto rico
+ Previsualizar
+ Estilos
+
+
+ Editar plantilla
+ Insertar área de contenido
+ Insertar marcador de posición de área de contenido
+ Insertar objeto del diccionario
+ Insertar macro
+ Insertar campo de página de Umbraco
+ Plantilla principal
+ Guía rápida sobre las etiquetas de plantilla de Umbraco
+ Plantilla
+
+
+ Insertar control
+ Choose layout
+ Añade más filas
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Plantillas de Grid
+ Las plantillas son el área de trabajo para el editor de grids, normalmente sólo necesitas una o dos plantillas diferentes
+ Añadir plantilla de grid
+ Ajusta la plantilla configurando la anchura de las columnas y añadiendo más secciones
+
+ Configuraciones de filas
+ Las filas son celdas predefinidas que se disponen horizontalmente
+ Añade una configuración de fila
+ Ajusta la fila configurando los anchos de cada celda y añadiendo más celdas
+
+ Columnas
+ Número total de columnas en la plantilla del grid
+
+ Configuración
+ Configura qué ajustes pueden cambiar los editores
+
+
+ Estilos
+ Configura qué estilos pueden cambiar los editores
+
+ La configuración sólo se guardará si el json introducido es válido
+
+ Permitir todos los controles de edición
+ Permitir todas las configuraciones de fila
+
+
+ Campo opcional
+ Texto opcional
+ MAYÚSCULA/minúscula
+ Elegir campo
+ Convertir a salto de línea
+ Reemplaza los saltos de línea con la etiqueta HTML <br>
+ Si, solamente la fecha
+ Cambiar formato a fecha
+ Codificar HTML
+ Se reemplazarán los caracteres especiales por su código HTML equivalente.
+ Será insertado después del valor del campo
+ Será insertado antes del valor del campo
+ Minúscula
+ Ninguno/ninguna
+ Insertar después del campo
+ Insertar antes del campo
+ Recursivo
+ Borrar los tags del párrafo
+ Borrará cualquier <P> al principio y al final del texto
+ Mayúscula
+ Codificar URL
+ Formateará los caracteres especiales de las URLs
+ Sólo será usado cuando el campo superior esté vacio
+ Este campo será usado unicamente si el campo primario está vacío
+ Si, con el tiempo. Separador:
+
+
+ Tareas asignadas a usted
+ asignadas a usted. Para acceder a la vista detallaa incluyendo comentarios, haga click sobre "Detalles" o sobre el nombre de la página. También puede descargar la página como XML directamente pulsando sobre el enlace "Descarga XML". Para terminar la tarea de traducción, por favor dirijase a la vista de detalles y haga click sobre el botón de "Cerrar". ]]>
+ cerrar tarea
+ Detalles de traducción
+ Descargar todas las tareas pendientes de traducción como archivo xml
+ Descargar xml
+ Descargar xml DTD
+ Campos
+ Incluir subpáginas
+
+ Tarea para tradudir [%0%] por %1%
+ No se encontraron usuarios traductores. Por favor, crea un usuario traductor antes de empezar a mandar contenido para su traducción
+ Tareas creadas por ti
+ creadas por tí. Para ver una vista detallada incluyendo los comentarios, pulsa en "Detalles" o tan solo en el nombre de la página. También puedes descargar la página como XML directamente pulsando en el enlace "Descargar Xml". Para cerrar una tarea de traducción, por favor ve a la vista de Detalles y pulsa el botón de "Cerrar".]]>
+ La página '%0%' se ha mandado a traducción
+ Manda la página '%0%' a traducción
+ Asignada por
+ Tarea abierta
+ Total de palabras
+ Traducir a
+ Traducción hecha.
+ Puedes previsualizar las páginas que acabas de traducir, pulsando debajo. Si la página original existe, se mostrará una comparación de las 2 páginas.
+ La traducción ha fallado. El archivo xml es inválido
+ Opciones para traducir
+ Traductor
+ Subir traducción xml
+
+
+ Caché del navegador
+ Papelera de reciclaje
+ Paquetes creados
+ Tipos de datos
+ Diccionario
+ Paquetes instalados
+ Instalar skin
+ Instalar starter kit
+ Idiomas
+ Instalar paquete local
+ Macros
+ Tipos de medios
+ Miembros
+ Grupos de miembros
+ Roles
+ Tipos de miembros
+ Tipos de documento
+ Paquetes
+ Paquetes
+ Ficheros Python
+ Instalar desde repositorio
+ Instalar pasarela
+ Módulos pasarela
+ Ficheros de script
+ Scripts
+ Hojas de estilo
+ Plantillas
+ Archivos XSLT
+
+
+ Existe una nueva actualización
+ %0% esta listo, pulsa aquí para descargar
+ No hay conexión al servidor
+ Error al comprobar la actualización. Por favor revisa "trace-stack" para conseguir más información.
+
+
+ Administrador
+ Campo de categoria
+ Cambiar contraseña
+ Change Your Password
+ Confirm new password
+ Puede cambiar su contraseña para acceder al 'back office' de Umbraco rellenando el siguiente formulario y haciendo clic en el botón 'Cambiar contraseña'
+ Canal de contenido
+ Campo descriptivo
+ Deshabilitar usuario
+ Tipo de documento
+ Editor
+ Campo de citas
+ Idioma
+ Login
+ Nodo de comienzo en la libreria de medios
+ Secciones
+ Deshabilitar acceso a Umbraco
+ Contraseña
+ Reset password
+ Su contraseña ha sido cambiada
+ Por favor confirme su nueva contraseña
+ Introduzca su nueva contraseña
+ La nueva contraseña no puede estar vacía
+ Current password
+ Invalid current password
+ La nueva contraseña no coincide con la contraseña de confirmación. Por favor, vuela a intentarlo!'
+ La contraseña de confirmación no coincide con la nueva contraseña!'
+ Reemplazar los permisos de los nodos hijo
+ Estas modificando los permisos para las páginas:
+ Selecciona las páginas para modificar sus permisos
+ Buscar en todos los hijos
+ Nodo de comienzo en contenido
+ Nombre de usuario
+ Permisos de usuarios
+ Tipo de usuario
+ Tipos de usuarios
+ Redactor
+ Tu perfil
+ Tu historial reciente
+ La sesión caduca en
+
+
diff --git a/WebCms/Umbraco/Config/Lang/fr.xml b/WebCms/Umbraco/Config/Lang/fr.xml
new file mode 100644
index 0000000..250d8cc
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/fr.xml
@@ -0,0 +1,1070 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Gérer les noms d'hôtes
+ Informations d'audit
+ Parcourir
+ Changer le type de document
+ Copier
+ Créer
+ Créer un package
+ Supprimer
+ Désactiver
+ Vider la corbeille
+ Exporter un type
+ Importer un type
+ Importer un package
+ Editer dans Canvas
+ Déconnexion
+ Déplacer
+ Notifications
+ Accès public
+ Publier
+ Dépublier
+ Rafraîchir
+ Republier le site entier
+ Permissions
+ Version antérieure
+ Envoyer pour publication
+ Envoyer pour traduction
+ Trier
+ Envoyer pour publication
+ Traduire
+ Mettre à jour
+ Valeur par défaut
+
+
+ Permission refusée.
+ Ajouter nouveau domaine
+ Supprimer
+ Noeud invalide.
+ Domaine invalide.
+ Domaine déjà assigné.
+ Domaine
+ Langage
+ Nouveau domaine '%0%' créé
+ Domaine '%0%' supprimé
+ Domaine '%0%' déjà assigné
+ Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela
+ devrait être évité. Utilisez plutôt la gestion des noms d'hôte.]]>
+ Domaine '%0%' mis à jour
+ Editer les domaines
+ Hériter
+ Culture
+ ou hériter la culture des noeuds parent. S'appliquera aussi'
+ au noeud courant, à moins qu'un domaine soit appliqué aussi.]]>
+ Domaines
+
+
+ Voir pour
+
+
+ Choisir
+ Choisir le répertoire courant
+ Faire autre chose
+ Gras
+ Annuler l'indentation de paragraphe
+ Insérer champ de formulaire
+ Insérer une entête graphique
+ Editer le HTML
+ Indenter le paragraphe
+ Italique
+ Centrer
+ Justifier à gauche
+ Justifier à droite
+ Insérer un lien
+ Insérer une ancre
+ Liste à puces
+ Liste numérique
+ Insérer une macro
+ Insérer une image
+ Editer les relations
+ Sauver
+ Sauver et publier
+ Sauver et envoyer pour approbation
+ Prévisualiser
+ La prévisualisation est désactivée car aucun modèle n'a été assigné.
+ Choisir un style
+ Afficher les styles
+ Insérer un tableau
+
+
+ Pour changer le type de document du contenu séléctionné, choisissez un type valide pour cet emplacement, qui soit conforme à la structure des types de documents.
+ Puis modifiez si nécessaire le mappage des propriétés du type actuel vers le nouveau, et cliquez sur Sauver
+ Le contenu a été republié.
+ Propriété actuelle
+ Type actuel
+ Le type de document ne peut être changé, il n'y a pas d'alternatives valides pour cet emplacement.
+ Type de document modifié
+ Mapper les propriétés
+ Mapper à la propriété
+ Nouveau modèle
+ Nouveau type
+ aucun
+ Contenu
+ Choisir le nouveau Type de Document
+ Le type de document du contenu séléctionné a a bien été changé en [new type] et les propriétés suivantes mappées :
+ en
+ Impossible de terminer le mappage des propriétés car une ou plus des propriétés ont plus de un mappage défini.
+ Seuls les types de documents valides pour cet emplacement sont affichés.
+
+
+ A propos de cette page
+ Alias
+ (comment décririez-vous l'image oralement)
+ Liens alternatifs
+ Cliquez pour éditer cet élément
+ Créé par
+ Auteur original
+ Mis à jour par
+ Créé
+ Date/heure à laquelle ce document a été créé
+ Type de Document
+ Edition
+ Expire le
+ Cet élément a été changé après la publication
+ Cet élément n'est pas publié
+ Dernière publication
+ Il n'y a aucun élément à afficher dans cette liste.
+ Type de Média
+ Lien vers des média(s)
+ Groupe de membres
+ Rôle
+ Type de membre
+ Aucune date choisie
+ Titre de page
+ Propriétés
+ Ce document est publié mais n'est pas visible car son parent '%0%' est dépublié
+ Oups : ce document est publié mais n'est pas présent dans le cache (erreur interne Umbraco)
+ Publier
+ Statut de publication
+ Publié le
+ Dépublié le
+ Supprimer la date
+ Ordre de tri mis à jour
+ Pour trier les noeuds, faites les glisser à l'aide de la souris ou cliquez sur les entêtes de colonne. Vous pouvez séléctionner plusieurs noeuds en gardant la touche "shift" ou "ctrl" enfoncée pendant votre séléction.
+ Statistiques
+ Titre (optionnel)
+ Type
+ Dépublié
+ Dernière édition
+ Date/heure à laquelle ce document a été édité
+ Supprimer fichier(s)
+ Lien vers un document
+ Membre du/des groupe(s)
+ Pas un membre du/des groupe(s)
+ Elements enfants
+ Cible
+
+
+ Cliquez pour télécharger
+ Faites glisser vos fichier ici...
+
+
+ Où voulez-vous créer le nouveau %0%
+ Créer un élément sous
+ Choisissez un type et un titre
+ "Types de documents".]]>
+ "Types de médias".]]>
+
+
+ Parcourir votre site
+ - Cacher
+ Si Umbraco ne s'ouvre pas, vous devriez peut-être autoriser l'ouverture des popups pour ce site.
+ s'est ouvert dans une nouvelle fenêtre
+ Redémarrer
+ Visiter
+ Bienvenue
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Name
+ Gérer les noms d'hôtes
+ Fermer cette fenêtre
+ Êtes-vous sûr de vouloir supprimer
+ Êtes-vous sûr de vouloir désactiver
+ Cochez cette case pour confirmer la suppression de %0% élément(s)
+ Êtes-vous sûr ?
+ Êtes-vous sûr ?
+ Couper
+ Editer une entrée du Dictionnaire
+ Editer le langage
+ Insérer une ancre
+ Insérer un caractère
+ Insérer une entête graphique
+ Insérer une image
+ Insérer un lien
+ Insérer une macro
+ Insérer un tableau
+ Dernière modification
+ Lien
+ Lien interne :
+ Si vous utilisez des ancres, insérer # avant le lien
+ Ouvrir dans une nouvelle fenêtre ?
+ Paramètres de macro
+ Cette macro ne contient aucune propriété éditable
+ Coller
+ Editer les permissions pour
+ Les éléments dans la corbeille sont en cours de suppression. Ne fermez pas cette fenêtre avant que cette opération soit terminée.
+ La corbeille est maintenant vide
+ Les éléments supprimés de la corbeille sont supprimés définitivement
+ regexlib.com a actuellement des problèmes sur lesquels nous n'avons aucun contrôle. Excusez-nous pour le désagrément.]]>
+ Rechercher une expression régulière Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url'
+ Supprimer la macro
+ Champ requis
+ Le site a été réindéxé
+ Le cache du site a été mis à jour. Tous les contenus publiés sont à jour. Et tous les contenus dépubliés sont rendus invisibles.
+ Le cache du site va être mis à jour. Tous les contenus publiés seront à jour. Et tous les contenus dépubliés seront rendus invisibles.
+ Nombre de colonnes
+ Nombre de lignes
+ Définir un placeholder id en mettant un ID sur votre placeholder vous pouvez injecter du contenu à cet endroit depuis vos modèles enfants,
+ en faisant référence à cet id au sein d'un élément <asp:content />.]]>
+ Séléctionnez un placeholder id depuis la liste ci-dessous. Vous pouvez seulement
+ choisir un ID depuis le modèle parent.]]>
+ Cliquez pour voir l'image en taille maximale
+ Séléctionner un élément
+ Voir l'élément de cache
+
+
+ %0%' ci-dessous Vous pouvez ajouter d'autres langages depuis le menu ci-dessous "Langages".
+ ]]>
+ Nom de Culture
+
+
+ Votre nom d'utilisateur
+ Votre mot de passe
+ Nommer %0%...
+ Entrez un nom...
+ Rechercher...
+ Filtrer...
+
+
+ Types de noeuds enfants autorisés
+ Créer
+ Supprimer onglet
+ Description
+ Nouvel onglet
+ Onglet
+ Miniature
+ Activer la vue liste
+
+
+ Ajouter une prévaleur
+ Type de données en base de donées
+ GUID du Property Editor
+ Property editor
+ Boutons
+ Activer les paramètres avancés pour
+ Activer le menu contextuel
+ Taille maximale par défaut des images insérées
+ CSS relatives
+ Afficher le libellé
+ Largeur et hauteur
+
+
+ Vos données ont été sauvegardées, mais avant de publier votre page il y a des erreurs que vous devez corriger :
+ Le Membership Provider n'autorise pas le changement des mots de passe (EnablePasswordRetrieval doit être définit à true)
+ %0% existe déjà
+ Il y a des erreurs :
+ Il y a des erreurs :
+ Le mot de passe doit contenir un minimum de %0% caractères et contenir au moins %1% caractère(s) non-alphanumerique
+ %0% doit être un entier
+ Le champ %0% dans l'onglet %1% est obligatoire
+ %0% est un champ obligatoire
+ %0% dans %1% n'est pas correctement formaté
+ %0% n'est pas correctement formaté
+
+
+ Le type de fichier spécifié n'est pas autorisé par l'administrateur
+ NOTE ! Même si CodeMirror est activé dans la configuration, il est désactivé sur Internet Explorer car il n'est pas stable sur ce navigateur.
+ Remplissez l'alias et le nom de la nouvelle propriété
+ Il y a un problème de droits de lecture/écriture sur un fichier ou dossier spécifique
+ Entrez un titre
+ Choisissez un type
+ Vous allez définir une taille d'image supérieure à sa taille d'origine. Êtes-vous sûr ?
+ Erreur dans le script Python
+ Le script Python n'a pas été sauvegardé, car il contient des erreur(s)
+ Noeud de départ supprimé, contactez votre administrateur
+ Séléctionnez du contenu avant de changer le style
+ Aucun style actif disponible
+ Placez le curseur à gauche des deux cellules que vous voulez fusionner
+ Vous ne pouvez pas scinder une cellule que vous n'avez pas fusionné.
+ Erreur dans le code source XSLT
+ Le XSLT n'a pas été sauvegardé, car il contient des erreur(s)
+ Il y a une erreur de configuration du type de données de cette propriété. Vérifiez ce type de données.
+
+
+ A propos
+ Action
+ Actions
+ Ajouter
+ Alias
+ Êtes-vous sûr ?
+ Bordure
+ par
+ Annuler
+ Marge de cellule
+ Choisir
+ Fermer
+ Fermer la fenêtre
+ Commenter
+ Confirmer
+ Contraindre les proportions
+ Continuer
+ Copier
+ Créer
+ Base de données
+ Date
+ Defaut
+ Supprimer
+ Supprimé
+ Suppression...
+ Design
+ Dimensions
+ Bas
+ Télécharger
+ Editer
+ Edité
+ Elements
+ Email
+ Erreur
+ Trouver
+ Hauteur
+ Aide
+ Icone
+ Importer
+ Marge intérieure
+ Insérer
+ Installer
+ Justifier
+ Langage
+ Layout
+ Chargement
+ Fermé
+ Connexion
+ Déconnexion
+ Déconnexion
+ Macro
+ Déplacer
+ Plus
+ Nom
+ Nouveau
+ Suivant
+ Non
+ de
+ OK
+ Ouvrir
+ ou
+ Mot de passe
+ Chemin
+ Placeholder ID
+ Un moment s'il vous plaît...
+ Précédent
+ Propriétés
+ Email de réception des données de formulaire
+ Corbeille
+ Restant
+ Renommer
+ Renouveller
+ Requis
+ Réessayer
+ Permissions
+ Rechercher
+ Serveur
+ Montrer
+ Afficher la page à l'envoi
+ Taille
+ Trier
+ Submit
+ Type
+ Rechercher...
+ Haut
+ Mettre à jour
+ Upgrader
+ Télécharger
+ Url
+ Utilisateur
+ Nom d'utilisateur
+ Valeur
+ Voir
+ Bienvenue...
+ Largeur
+ Oui
+ Dossier
+ Résultats de recherche
+ Reorder
+ I am done reordering
+
+
+ Background color
+ Gras
+ Couleur de texte
+ Police
+ Texte
+
+
+ Page
+
+
+ L'installeur n'a pas pu se connecter à la base de données.
+ Impossible de modifier le fichier web.config file. Modifiez s'il vous plait la "connection string" manuellement.
+ Votre base de données a été détectée et identifiée telle que
+ Configurtion de la base de données
+ install pour installer la base de données Umbraco %0%
+ ]]>
+ Suivant pour procéder.]]>
+ Base de données non trouvée ! Vérifiez les informations de la "connection string" dans le fichier web.config.
+
Pour poursuivre, éditez le fichier "web.config" (avec Visual Studio ou votre éditeur de texte favori), scrollez jusqu'en bas, ajoutez une "connection string" dans la ligne "umbracoDbDSN" et sauvegardez votre fichier.
]]>
+
+ Contactez votre administrateur système si nécessaire.
+ Si vous installez Umbraco sur votre ordinateur, vous aurez probablement besoin de consulter votre administrateur système.]]>
+
+ Appuyez sur le bouton Upgrader pour mettre à jour votre base de données vers Umbraco %0%
+
+ Ne vous inquiétez pas : aucun contenu ne sera supprimé et tout continuera à fonctionner parfaitement ensuite !
+
+ ]]>
+ Appuyez sur Suivant pour
+ poursuivre. ]]>
+ Suivant pour poursuivre la configuration]]>
+ Le mot de passe par défaut doit être changé !]]>
+ L'utilisateur par défaut a été désactivé ou n'a pas accès à Umbraco!
Aucune action n'est requise. Cliquez sur Suivant pour poursuivre.]]>
+ Le mot de passe par défaut a été modifié avec succès !
Aucune action n'est requise. Cliquez sur Suivant pour poursuivre.]]>
+ Le mot de passe a été changé !
+
+ Umbraco créer un utilisateur par défaut avec le login ('admin') et le mot de passe ('default').
+ Il est important que ce mot de passe soit changé pour quelque-chose de sécurisé et unique.
+
+
+ Cette étape va vérifier le mot de passe par défaut et vérifier s'il est nécessaire de le changer.
+
+ ]]>
+ Pour bien commencer, regardez nos vidéos d'introduction
+ En cliquant sur le bouton Suvant (ou en modifiant umbracoConfigurationStatus dans le fichier web.config), vous acceptez la licence de ce logiciel telle que spécifiée dans le champ ci-dessous. Remarque : cette distribution Umbraco consiste en deux licences différentes, la licence open source MIT pour le framework et la licence Umbraco freewarequi couvre l'UI.
+ Pas encore installé.
+ Fichiers et dossiers affectés
+ Plus d'informations sur la définition des permissions
+ Vous devez donner à ASP.NET les droits de modification sur les fichiers/dossiers suivants
+ Vos permissions sont presques parfaites !
+ Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement parti d'Umbraco.]]>
+ Comment le résoudre
+ Cliquez ici pour lire la version texte
+ tutoriel vidéo sur la définition des permissions pour Umbraco, ou lisez la version texte.]]>
+ Vos permissions pourraient être problématiques !
+
+ Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement parti d'Umbraco.]]>
+ Vos permissions ne sont pas prêtes pour Umbraco !
+
+ Pour faire fonctionner Umbraco, vous aurez besoin de mettre à jour les permissions sur les fichiers/dossiers.]]>
+ Vos permissions sont parfaites !
+ Vous êtes prêt à faire fonctionner Umbraco et installer des packages !]]>
+ Résoudre un problème sur un dossier
+ Suivz ce lien pour plus d'informations sur ASP.NET et la création de dossiers
+ Définir les permissions de dossier
+
+ Je veux démarrer "from scratch"
+ Apprenez comment)
+ Vous pouvez toujours choisir d'installer Runway plus tard. Pour cela allez dans la séction "Développeur" et séléctionnez "Packages".
+ ]]>
+ Vous avez mis en oeuvre une plateforme Umbraco clean. Que voulez-vous faire ensuite ?
+ Runway est installé
+
+ Voici la liste des modules recommandés, cochez ceux que vous souhaitez installer, ou regardez la liste complète des modules
+ ]]>
+ Recommandé uniquement pour les utilisateurs expérimentés
+ Je veux commencer avec un site simple
+
+ "Runway" est un simple site fournissant des types de documents et modèles basiques. L'installeur peut mettre en oeuvre Runway automatiquement pour vous,
+ mais vous pouvez facilement léditer, lenrichir, ou le supprimer ensuite. Il n'est pas nécessaire et vous pouvez parfaitement utiliser Umbraco sans. Pour autant,
+ Runway offre un socle facile basé sur des bonnes pratiques pour vous permettre de commencer rapidement.
+ Si vous choisissez d'installer Runway, vous pouvez, de manière optionnelle, choisir des blocks appelés Runway Modules pour enrichir les pages du site.
+
+
+ Inclus avec Runway : Home page, Getting Started page, Installing Modules page.
+ Modules optionnels : Top Navigation, Sitemap, Contact, Gallery.
+
+ ]]>
+ Qu'est ce que Runway
+ Step 1/5 : Licence
+ Step 2/5 : Configuration base de données
+ Step 3/5 : Validation des permissions de fichiers
+ Step 4/5 : Sécurité Umbraco
+ Step 5/5 : Umbraco est prêt
+ Merci d'avoir choisi Umbraco
+ Parcourir votre nouveau site
+Vous avez installé Runway, alors pourquoi pas jeter un oeil au look de votre nouveau site ?]]>
+ Aide et information
+Obtenez de l'aide de notre award winning communauté, parcourez la documentation our regardez quelques vidéos sur "Comemnt construire un site simple", "Comment utiliser les packages" et un guide rapide sur la terminologie Umbraco]]>
+ Umbraco %0% est installé et prêt à être utilisé
+ /web.config file et mettre à jour le paramètre AppSetting dans umbracoConfigurationStatus en bas de la valeur de '%0%'.]]>
+ démarrer maintenant en cliquant sur le bouton "Lancer Umbraco" ci-dessous.
+Si vous débutez sur Umbraco, vous pouvez trouver plein de ressources sur nos pages "Getting Started".]]>
+ Lancer Umbraco
+Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à ajouter du contenu, mettez à jour les templates et feuilles de styles ou ajoutez ds nouvelles fonctionnalités]]>
+ La connexion à la base de données a échoué.
+ Umbraco Version 3
+ Umbraco Version 4
+ Regarder
+ umbraco %0%, qu'il s'agisse d'une installation récente ou de la version 3.0
+
]]>
+ La notification [%0%] à propos de %1% a été executée sur %2%
+ Notifications
+
+
+
+ localisez le package. Les packages Umbraco ont généralement une extension .umb ou .zip.
+ ]]>
+ Auteur
+ Demon
+ Documentation
+ Meta data du package
+ Nom du package
+ Le package ne contient aucun éléments
+
+ Vous pouvez supprimer tranquillement le package de votre installation en cliquant sur "Désinstaller" ci-dessous.]]>
+ Aucune mises à jour disponibles
+ Options de package
+ Package readme
+ Package repository
+ Confirmation de désinstallation
+ Package was uninstalled
+ Le package a été désinstallé avec succès
+ Désintaller le package
+
+ Remarque : tous les documents, media etc dépendants des éléments que vous supprimerez, arrêteront de fonctionner, ce qui peut provoquer une instabilité du système,
+ désinstallez avec prudence. En cas de doute, contactez l'auteur du package.]]>
+ Télécharger la mise à jour depuis le repository
+ Mettre à jour le package
+ Instructions de mise à jour
+ Il y a une mise à jour disponible pour ce package. Vous pouvez la télécharger directement depuis le repository.
+ Version de package
+ Historique des version de package
+ Voir le site internet du package
+
+
+ Coller en conservant le formatage (non recommandé)
+ Le texte que vous tentez de coller contient des caractères spéciaux ou de formatage. Cela peut être dû à une copie d'un texte de Microsoft Word. Umbraco peut supprimer les caractères spéciaux et le formatage automatiquement, de manière à ce que le texte collé soit plus utilisable pour le Web.
+ Coller en tant que texte brut sans formatage
+ Coller, mais supprimer le formatage (recommendé)
+
+
+ Protection basée sur les rôles
+ via les groupes de membres Umbraco.]]>
+ l'authentification basée sur les rôles.]]>
+ Page d'erreur
+ Utilisé quand les gens sont connectés, mais n'ont pas accès
+ Choisissez comment restreindre l'accès à cette page
+ %0% est maintenant protégée
+ Protection supprimée de %0%
+ Page de connexion
+ Choisissez la page qui a le formulaire de login
+ Supprimer la protection
+ Choisissez la page qui contient le formulaire de login et les messages d'erreur
+ Piochez les roles qui ont accès à cette page
+ Définissez l'identifiant et mot de passe pour cette page
+ Protection utilisateur unique
+ Si vous souhaitez simplement mettre en place une protection par identifiant et mot de passe
+
+
+
+
+
+
+
+
+ Inclure les pages enfants non publiées
+ Publication en cours - veuillez patienter...
+ %0% sur %1% des pages ont été publiées...
+ %0% a été publié
+ %0% et ses pages enfants ont été publiées
+ Publier %0% et ses pages enfants
+ ok pour publier %0% et le rendre ainsi accessible publiquement.
+ Vous pouvez publier cette page et ses sous pages en cochant publier tous les enfants ci-dessous.
+ ]]>
+
+
+ Vous n'avez configuré aucune couleurs approuvées
+
+
+ Ajouter un lien externe
+ Ajouter un lien interne
+ Ajouter
+ Légende
+ Page interne
+ URL
+ Descendre
+ Monter
+ Ouvrir dans une nouvelle fenêtre
+ Supprimer le lien
+
+
+ Version actuelle
+ Le texte en Rouge signifit qu'il a été supprimé de la version choisie, vert signifie ajouté]]>
+ Le document est passé à une version antérieure
+ Ceci affiche la version choisie en tant que HTML, si vous souhaitez voir les différences entre les deux versions en même temps, utilisez la vue différentielle
+ Revenir à
+ Choisissez une version
+ Voir
+
+
+ Editer le fichier de script
+
+
+ Concierge
+ Contenu
+ Courier
+ Développeur
+ Assistant de configuration Umbraco
+ Medias
+ Membres
+ Newsletters
+ Paramètres
+ Statistiques
+ Traduction
+ Utilisateurs
+ Aide
+
+
+ Template par défaut
+ Clé de dictionnaire
+ Pour importer un type de document, trouvez le fichier ".udt" sur votre ordinateur en cliquant sur le bouton "Parcourir" et cliquez sur "Importer" (une confirmation vous sera demandé à l'écran d'après)
+ Titre du nouvel onglet
+ Type de noeud
+ Type
+ Feuille de style
+ Script
+ Propriété de feuille de style
+ Onglet
+ Titre de l'onglet
+ Onglets
+ Type de contenu master activé
+ Ce type de contenu utilise
+ en tant que type de contenu master, les onglets du type de contenu master ne sont pas affichés et peuvent seulement être modifiés dans le type de contenu master lui-même.
+ Aucune propriétés définies dans cet onglet. Cliquez sur le lien "Ajouter une nouvelle propriété" en haut pour créer une nouvelle propriété.
+ Type de contenu parent
+ Créer le template correspondant
+
+
+ Sort order
+ Creation date
+ Tri achevé.
+ Faites glisser les différents éléments ci-dessous vers le haut ou le bas pour définir comment ils doivent être triés. Ou cliquez sur les entêtes de colonne pour trier la collection complète d'éléments
+ Ne fermez pas cette fenêtre durant le tri.]]>
+
+
+ La publication a été annulée par un extension tierce.
+ Le type de propriété existe déjà
+ Type de propriété créé
+ Type de données : %1%]]>
+ Type de propriété supprimé
+ Type de documet sauvegardé
+ Onglet créé
+ Onglet supprimé
+ Onglet d'ID : %0% supprimé
+ Feuille de style non sauvegardée
+ Feuille de style sauvegardée
+ Feuille de style sauvegardée sans erreurs
+ Type de données sauvegardée
+ Element de dictionnaire sauvegardé
+ La publication a échoué car la page parent n'est pas publiée
+ Contenu publié
+ et visible sur le site
+ Content sauvegardé
+ N'oubliez pas de publier pour rendre les changements visibles
+ Envoyer pour approbation
+ Les changements ont été envoyés pour approbation
+ Media sauvegardé
+ Media sauvegardé sans erreurs
+ Membre sauvegardé
+ Propriété de feuille de style sauvegardé
+ Feuille de style sauvegardé
+ Template sauvegardé
+ Erreur lors de la sauvegarde de l'utilisateur
+ Utilisateur sauvegardé
+ Type d'utilisateur sauvegardé
+ Fichier non sauvegardé
+ Le fichier n'a pas pu être sauvegardé. Vérifiez les permissions de fichier.
+ Fichier sauvegardé
+ Fichier sauvegardé sans erreurs
+ Langage sauvegardé
+ Le script Python n'a pas été sauvegardé
+ Le script Python n'a pas été sauvegardé à cause d'erreurs
+ Le script Python a été sauvegardé
+ Aucune erreur dans le script Python
+ Modèle non sauvegardé
+ Assurez-vous de ne pas avoir 2 modèles avec le même alias.
+ Modèle sauvegardé
+ Modèle sauvegardé sans aucune erreurs !
+ Le XSLT n'a pas été sauvegardé
+ Le XSLT contenait une erreur
+ Le XSLT n'a pas pu être sauvegardé, vérifiez les permissions de fichier
+ Le XSLT a été sauvegardé
+ Aucune erreurs dans le XSLT
+ Contenu publié
+ Vue partielle sauvegardée
+ Vue partielle sauvegardée sans erreurs !
+ Vue partielle non sauvegardée
+ Une erreur est survenue lors de la sauvegarde du fichier.
+
+
+ Utilise la synthaxe CSS. Ex : h1, .redHeader, .blueTex
+ Editer la feuille de style
+ Editer la propriété de feuille de style
+ Nommer pour identifier la propriété dans le Rich Text Editor
+ Prévisualiser
+ Styles
+
+
+ Editer le modèle template
+ Insérer une zone de contenu
+ Insérer un placeholder de zone de contenu
+ Insérer un élément de dictionnaire
+ Insert Macro
+ Insert umbraco page field
+ Modèle master
+ Guide rapide aux tags des modèles Umbraco
+ Modèle
+
+
+ Choisissez le type de contenu
+ Choisissez une mise en page
+ Ajouter une ligne
+ Ajouter du contenu
+ Contenu goutte
+ Paramètres appliqués
+
+ Ce contenu est pas autorisée ici
+ Ce contenu est permis ici
+
+ Cliquez pour intégrer
+ Cliquez pour insérer l'image
+ Légende de l'image...
+ Ecrire ici...
+
+ Layouts Grid
+ Layouts sont la superficie totale de travail pour l'éditeur de grille, en général, vous avez seulement besoin d'une ou deux configurations différentes
+ Ajouter Grid Layout
+ Ajustez la mise en page en définissant la largeur des colonnes et ajouter des sections supplémentaires
+ Configurations des lignes
+ Les lignes sont des cellules prédéfinies disposées horizontalement
+ Ajouter une configuration de la ligne
+ Ajustez la ligne en réglant la largeur des cellules et en ajoutant des cellules supplémentaires
+
+ Colonnes
+ Nombre total combiné de colonnes dans la configuration de la grille
+
+ Paramètres
+ Configurer quels paramètres éditeurs peuvent changer
+
+ Modes
+ Configurer ce style éditeurs peuvent changer
+
+ Les réglages seulement économiser si la configuration du json saisi est valide
+
+ Autoriser tous les éditeurs
+ Autoriser toutes les configurations de lignes
+
+
+ Champ alternatif
+ Texte alternatif
+ Casse
+ Encodage
+ Choisir un champ
+ Convertir les sauts de ligne
+ Remplace les sauts de ligne avec des balises <br>
+ Champs particuliers
+ Oui, la date seulement
+ Formater comme une date
+ Encoder en HTML
+ Remplacera les caractères spéciaux par leur équivalent HTML.
+ Sera insérer après la valeur du champ
+ Sera inséré après la valeur du champ
+ Minuscules
+ Aucun
+ Inserer après le champ
+ Inserer après le champ
+ Recursif
+ Supprimer les balises paragraphes
+ Supprimera tous les <P>
+ Champs standards
+ Majuscules
+ Encode en URL
+ Formatera les caractères spéciaux de manière à ce qu'ils soient utilisés dans une URL
+ Sera seulement utilisé quand toutes les valeurs ci-dessous seront vides
+ Ce champ sera utilisé seulement si le champ primaire est vide
+ Yes, with time. Separator:
+
+
+ Tâches qui vous sont assignées
+ vous sont assignées. Pour voir une vue détaillée incluant les commentaires, cliquez sur "Details" ou juste le nom de la page.
+ Vous pouvez télécharger la format au format XML en cliquant sur le lien "Télécharger XML".
+ Pour terminer une tâche de traduction, allez sur Details, puis cliquer sur le bouton "Terminer tâche".
+ ]]>
+ Terminer tâche
+ Détails
+ Télécharger toutes les traductions au format XML
+ Télécharger XML
+ Télécharger la DTD XML
+ Champs
+ Inclure les pages enfants
+
+ [%0%] tâches de traductions pour %1%
+ Aucun utilisateurs traducteurs trouvés. Vous devez créer un utilisateur traducteur avant d'envoyer votre contenu pour traduction
+ Tâches que vous avez créé
+ créées par vous. Pour voir une vue détaillée incluant les commentaires,
+ cliquez sur "Détails" ou juste le nom de la page. Vous pouvez aussi télécharger la page au format XML en cliquant sur le lien "Télécharger XML".
+ Pour terminer une tâche de traduction, allez sur Details, puis cliquer sur le bouton "Terminer tâche".
+ ]]>
+ La page '%0%' a été envoyé pour traduction
+ Envoyer la page '%0%' pour traduction
+ Assignée par
+ Tâches ouvertures
+ Nombre total de mots
+ Traduire en
+ Traduction complétée.
+ Vous pouvez prévisualiser les pages que vous avez traduites, en cliquant ci-dessous. Si la page originale est trouvée, vous aurez la comparaison entre les deux pages.
+ Traductio échouée, il semble que le fichier XML soit corrompu
+ Options de traduction
+ Traducteur
+ Uploader le fichier de traduction XML
+
+
+ Navigateur de cache
+ Corbeille
+ Packages créés
+ Typesde données
+ Dictionnaire
+ Packages installés
+ Installer un skin
+ Installer un starter kit
+ Langages
+ Installer un package local
+ Macros
+ Types de médias
+ Membres
+ Groupes de membres
+ Rôles
+ Types de membres
+ Types de documents
+ Packages
+ Packages
+ Fichiers Python
+ Installer depuis le repository
+ Installer Runway
+ Modules Runway
+ Fichiers de script
+ Scripts
+ Feuilles de style
+ Modèles
+ Fichiers XSLT
+
+
+ Nouvelle mise à jour prête
+ %0% est prêt, cliquez ici pour télécharger
+ Aucune connexion au serveur
+ Erreur lors de la recherche de mises à jour. Vérifiez la stack trace pour obtenir plus d'informations sur l'erreur.
+
+
+ Administrateur
+ Champ catégorie
+ Changer le mot de passe
+ Changez votre mot de passe
+ Confirmez votre nouveau mot de passe
+ Vous pouvez changer votre mot de passe d'accès à Umbraco en remplissant le formulaire ci-dessous puis en cliquant sur le bouton "Changer le mot de passe"
+ Canal de contenu
+ Champ description
+ Désactiver l'utilisateur
+ Type de document
+ Editeur
+ Champ extrait
+ Langage
+ Identifiant
+ Noeud de départ dans la librarie de médias
+ Sections
+ Désactiver l'accès Umbraco
+ Mot de passe
+ Réinitialiser le mot de passe
+ Your password has been changed!
+ Confirmez s'il vous plait votre nouveau mot de passe
+ Entrez votre nouveau mot de passe
+ Votre nouveau mot de passe ne peut être vide !
+ Mot de passe actuel
+ Mot de passe actuel invalide
+ Il y avait une différence entre le nouveau mot de passe et le mot de passe confirmé. Veuillez réessayer
+ Le mot de passe confirmé ne match pas le nouveau mot de passe saisi
+ Remplacer les permissions des noeuds enfants
+ Vous modifiez actuellement les permissions pour les pages :
+ Choisissez les pages pour lesquelles modifier les permissions
+ Rechercher tous les enfants
+ Noeud de départ du contenu
+ Nom d'utilisateur
+ Permissions utilisateur
+ Type d'utilisateur
+ Types d'utilisateur
+ Rédacteur
+ Votre profil
+ Votre historique récent
+ La session expire dans
+
+
diff --git a/WebCms/Umbraco/Config/Lang/he.xml b/WebCms/Umbraco/Config/Lang/he.xml
new file mode 100644
index 0000000..9731d10
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/he.xml
@@ -0,0 +1,947 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ נהל שמות מתחם
+ מעקב ביקורות
+ צפה בתוכן
+ העתק
+ צור
+ צור חבילה
+ מחק
+ נטרל
+ רוקן סל מיחזור
+ ייצא סוג קובץ
+ ייצא אל.NET
+ ייצא אל .NET
+ ייבא סוג מסמך
+ ייבא חבילה
+ ערוך במצב "קנבס"
+ יציאה
+ הזז
+ התראות
+ גישה ציבורית
+ פרסם
+ רענן פריטי תוכן
+ פרסם את כל האתר מחדש
+ הרשאות
+ חזור לאחור
+ שלח לפירסום
+ שלח לתירגום
+ מיין
+ שלח לפירסום
+ תרגם
+ עדכן
+ מחק
+ בצע
+ תזמן
+ ייבא נתונים
+ צור הגדרות ילדים
+ יומן משימות מתוזמנות
+
+
+ הוסף דומיין חדש
+ דומיין
+ הדומיין החדש %0% נוסף בהצלחה
+ הדומיין %0% נמחק
+ הדומיין %0% כבר מוקצה
+ לדוגמא: yourdomain.com, www.yourdomain.com
+ הדומיין %0% עודכן בהצלחה
+ ערוך דומיין נוכחי
+
+
+ צופה עבור
+
+
+ מודגש
+ בטל מרחק שוליים מהפסקה
+ הוסף מתוך שדה
+ הוספת קו גרפי
+ ערוך -Html
+ מרחק שוליים מהפסקה
+ נטוי
+ ממורכז
+ מוצמד לשמאל
+ מוצמד לימין
+ הוספת לינק
+ הוספת לינק מקומי (עוגן)
+ רשימת תבליטים
+ רשימה ממוספרת
+ הוספת מקרו
+ הוספת תמונה
+ ערוך הקשר
+ שמור
+ שמור ופרסם
+ שמור ושלח לאישור
+ תצוגה מקדימה
+ בחר עיצוב
+ הצג עיצוב
+ הוספת טבלה
+
+
+ אודות עמוד זה
+ קישור חלופי
+ (תיאור התמונה בקצרה)
+ קישור חלופי
+ לחץ לעריכת פריט זה
+ נוצר על ידי
+ נוצר בתאריך
+ סוג מסמך
+ עריכה
+ הוסר ב
+ פריט זה שונה לאחר פירסומו
+ פריט זה לא פורסם
+ פורסם לאחרונה
+ סוג מדיה
+ קבוצת חברים
+ תפקיד
+ סוג חבר
+ לא נבחר מידע
+ כותרת עמוד
+ הגדרות
+ מסמך זה פורסם אך לא זמין לצפיה, עקב כך שמסמך האב '%0%' ממתין לפירסום
+ פרסם
+ סטטוס פירסום
+ פורסם ב
+ נקה מידע
+ סידור ממוין עודכן
+ כדי למיין את המסמכים, פשוט יש לגרור את המסמכים או ללחוץ על אחד מכותרות העמודות. ניתן לבחור מספר מסמכים בו זמנית על ידי לחיצת "Shift" או "Ctrl" בזמן הבחירה.
+ סטטיסטיקות
+ כותרת (לא חובה)
+ סוג
+ ממתין לפירסום
+ נערך לאחרונה
+ הסר קובץ
+ קשר למסמך
+
+
+ היכן ברצונך ליצור את %0%
+ צור ב
+ בחר סוג וכותרת
+
+
+ סייר באתר
+ - הסתר מידע לצמיתות
+ במידה ואומברקו לא פתוח, יש צורך לאשר חלונות קופצים מאתר זה.
+ נפתח בחלון חדש
+ הפעל מחדש
+ בקר
+ ברוכים הבאים
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ שם
+ ניהול שם מתחם
+ סגור חלון זה
+ האם הינך בטוח שברצונך למחוק זאת?
+ האם הינך בטוח שברצונך לכבות זאת?
+ סמן תיבה זו לאשר מחיקה סופית של %0% פריט/ים
+ האם הינך בטוח?
+ האם אתה בטוח?
+ גזור
+ ערוך פרט מילון
+ ערוך שפה
+ הוסף קישור מקומי
+ הוסף תו
+ הוסף פס גרפי
+ הוסף תמונה
+ הוסף קישור
+ לחץ להוספת מאקרו חדש
+ הוסף טבלה
+ נערך לאחרונה
+ קישור
+ קישור פנימי:
+ בעת שימוש בקישוריים פנימיים, הוסף "#" בתחילת הקישור
+ לפתוח בחלון חדש?
+ הגדרות מאקרו
+ המאקרו לא מכיל מאפיינים שניתן לערוך
+ הדבק
+ ערוך הרשאות עבור
+ הפריטים הנמצאים בסל המיחזור נמחקים כעת, השאר חלון זה פתוח עד לגמר פעולת המחיקה.
+ סל המיחזור ריק כעת
+ מחיקת פריטים מסל המיחזור תמחוק את הפריטים לצמיתות
+ regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]>
+ חיפוש ביטויים להוספת אימות עבור שדות טופס. לדוגמא: 'כתובת אימייל', 'מיקוד', 'כתובת אתר' ועוד
+ הסר מאקרו
+ שדה חובה
+ האתר אונדקס מחדש
+ זיכרון המטמון של האתר רוענן בהצלחה. כל התוכן המפורסם כעת מעודכן, שאר התוכן המיועד לפירסום ימתין לפירסום
+ זיכרון המטמון של האתר ירוענן. כל התוכן שפורסם ירוענן בהתאם, שאר התוכן המיועד לפירסום ימתין לפירסום
+ מספר עמודות
+ מספר שורות
+ Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates,
+ by refering this ID using a <asp:content /> element.]]>
+ Select a placeholder id from the list below. You can only
+ choose Id's from the current template's master.]]>
+ לחץ על התמונה לגודל מלא
+ בחר פריט
+ צפה בפרטי זיכרון מטמון
+
+
+ %0%' למטה ניתן להוסיף שפות נוספות תחת "שפות" בתפריט בצד שמאל
+ ]]>
+ שם התצוגה לשפה
+
+
+ תת פריטי תוכן מאושרים:
+ צור
+ מחק לשונית
+ תיאור
+ לשונית חדשה
+ לשונית
+ תמונה ממוזערת
+
+
+ הוסף ערך מקדים
+ סוג מידע עבור בסיס נתונים
+ Data Editor GUID
+ הפוך שליטה
+ כפתורים
+ הפעל הגדרות מתקדמות עבור
+ הפעל תפריט מקושר
+ גודל תמונה מקסימלי כברירת מחדל עבור תמונות המתווספות
+ סגנונות עיצוב קרובים
+ הצג תוויות
+ רוחב ואורך
+
+
+ המידע שלך נשמר, אך לפני שניתן יהיה לפרסם אותו יש צורך לתקן את השגיאות הבאות:
+ ספק החברות הנוכחית לא תומך בשינוי סיסמה (EnablePasswordRetrieval צריך להיות מוגדר על true)
+ השדה %0% כבר קיים
+ התרחשו שגיאות:
+ התרחשו שגיאות:
+ הסיסמה חייבת להיות במינימום של %0% תווים characters long and contain at least %1% non-alpha numeric character(s)
+ %0% חייב להיות מספר שלם
+ השדה %0% בלשונית %1% הינו זה חובה
+ השדה %0% הינו זה חובה
+ %0% ב- %1% אינו הפורמט התקין
+ %0% אינו פורמט תקין
+
+
+ שים לב! למרות ש- CodeMirror מופעל מההגדרות, הוא נמצא במצב כבוי באינטרנט אקספלורר מפאת חוסר יציבות.
+ אנא הזן את את הכינוי והשם עבור סוג המידע!
+ קיימת בעיית הרשאות גישה בקריאה/כתיבה עבור הקובץ או התיקיה הזו
+ אנא בחר כותרת
+ אנא בחר סוג
+ הינך עומד לשנות את התמונה לגודל גדול יותר מהמקור, האם ברצונך להמשיך
+ שגיאות ב- python script
+ ה- python script לא נשמר, מכיל שגיאות
+ הפריט תוכן ההתחלתי נמחק, צור קשר עם מנהל האתר.
+ תחילה יש לסמן תוכן לפני שינוי עיצוב
+ סגנונות עיצוב פעילים לא זמינים
+ יש למקם את הסמן משמאל לשני התאים אותם תרצה למזג
+ אין אפשרות לפצל תא שלא מוזג לפני כן.
+ שגיאה במקור XSLT
+ קובץ ה- XSLT לא נשמר, הקובץ מכיל שגיאות.
+
+
+ אודות
+ פעולה
+ הוסף
+ כינוי
+ האם אתה בטוח?
+ גבול
+ או
+ בטל
+ שוליים לתא
+ בחר
+ סגור
+ סגור חלון
+ הערה
+ אישור
+ שמור על פרופורציות
+ המשך
+ העתק
+ צור
+ בסיס נתונים
+ תאריך
+ ברירת מחדל
+ מחק
+ נמחק
+ מחיקה...
+ עיצוב
+ מימדים
+ למטה
+ הורדה
+ עריכה
+ נערך
+ אלמנטים
+ כתובת אימייל
+ שגיאה
+ חפש
+ אורך
+ עזרה
+ אייקון
+ ייבא
+ שוליים פנימיים
+ הוסף
+ התקנה
+ ליישר
+ שפה
+ תכנית
+ טוען
+ נעול
+ התחבר
+ התנתק
+ התנתק
+ מקרו
+ הזז
+ שם
+ חדש
+ הבא
+ לא
+ של
+ אישור
+ פתח
+ או
+ סיסמה
+ נתיב
+ Placeholder קוד זיהוי
+ אנא המתן בבקשה...
+ הקודם
+ הגדרות
+ כתובת אימייל לקבלת טופס
+ סל מיחזור
+ נשאר
+ שנה שם
+ חידוש
+ נסה שנית
+ הרשאות
+ חיפוש
+ שרת
+ הצג
+ הצג עמוד בשליחה
+ גודל
+ סדר
+ Submit
+ סוג
+ הקלד לחיפוש...
+ למעלה
+ עדכן
+ שדרג
+ העלאה
+ כתובת URL
+ משתמש
+ שם משתמש
+ ערך
+ צפיה
+ ברוכים הבאים...
+ רוחב
+ כן
+ Reorder
+ I am done reordering
+
+
+ צבע רקע
+ מובלט
+ צבע טקסט
+ פונט
+ טקסט
+
+
+ עמוד
+
+
+ ההתקנה לא מצליחה להתחבר לבסיס הנתונים.
+ אין אפשרות לשמור את הקובץ Web.config file. הגדר את ה- connection string באופן ידני.
+ בסיס הנתונים שלך נמצא והוא מזוהה כ
+ הגדרת בסיס נתונים
+ install button to install the Umbraco %0% database
+ ]]>
+ Next to proceed.]]>
+ Database not found! Please check that the information in the "connection string" of the “web.config” file is correct.
+
To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.
]]>
+
+ Please contact your ISP if necessary.
+ If you're installing on a local machine or server you might need information from your system administrator.]]>
+
+ Press the upgrade button to upgrade your database to Umbraco %0%
+
+ Don't worry - no content will be deleted and everything will continue working afterwards!
+
+ ]]>
+ Press Next to
+ proceed. ]]>
+ next to continue the configuration wizard]]>
+ The Default users’ password needs to be changed!]]>
+ The Default user has been disabled or has no access to Umbraco!
No further actions needs to be taken. Click Next to proceed.]]>
+ The Default user's password has been successfully changed since the installation!
No further actions needs to be taken. Click Next to proceed.]]>
+ הסיסמה שונתה!
+
+ Umbraco creates a default user with a login ('admin') and password ('default'). It's important that the password is
+ changed to something unique.
+
+
+ This step will check the default user's password and suggest if it needs to be changed.
+
+ ]]>
+ התחל מכאן, צפה בסרטוני ההדרכה עבור אומברקו
+ על ידי לחיצה על 'הבא', הנך מאשר את פרטי התקנון כפי שמפורט בתיבת הטקטס למטה. שים לב, הפצה זו של אומברקו כוללת שני גירסאות שונות של רשיון,קוד פתוח ברשיון MIT עבור ה- framework ורשיון Umbraco freeware המכסה את ה- UI.
+ לא הותקן עדיין.
+ קבצים ותיקיות המושפעים
+ מידע נוסף אודות התקנה ורשאות עבור אומרקו ניתן לקרוא כאן
+ על מנת לבצע זאת, יש צורך לאפשר הרשאות ל ASP.NET לערוך את הקצבים או התיקיות הבאות
+ Your permission settings are almost perfect!
+ You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
+ איך לפתור
+ לחץ כאן לקרוא את גירסת הטקסט
+ video tutorial on setting up folder permissions for Umbraco or read the text version.]]>
+ Your permission settings might be an issue!
+
+ You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
+ Your permission settings are not ready for Umbraco!
+
+ In order to run Umbraco, you'll need to update your permission settings.]]>
+ Your permission settings are perfect!
+ You are ready to run Umbraco and install packages!]]>
+ פתירת בעיות בתיקיה
+ עקוב אחר הלינק המצורף על מנת לפתור בעיות עם ASP.NET ויצירת תיקיות חדשות.
+ הגדרת הרשאות לתיקיה
+
+ אני רוצה להתחיל מאתר ריק
+ learn how)
+ You can still choose to install Runway later on. Please go to the Developer section and choose Packages.
+ ]]>
+ סיימת להתקין את מערכת אומברקו, מה ברצונך לעשות כעת?
+ Runway הותקן
+
+ This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules
+ ]]>
+ המלצות עבור משתמשים מנוסים
+ ברצוני להתחיל עם אתר פשוט
+
+ "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically,
+ but you can easily edit, extend or remove it. It’s not necessary and you can perfectly use Umbraco without it. However,
+ Runway offers an easy foundation based on best practices to get you started faster than ever.
+ If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.
+
+
+ Included with Runway: Home page, Getting Started page, Installing Modules page.
+ Optional Modules: Top Navigation, Sitemap, Contact, Gallery.
+
+ ]]>
+ מה זה Runway
+ צעד 1/5 אישור רשיון
+ צעד 2/5: הגדרת בסיס נתונים
+ צעד 3/5: אימות קובץ ההרשאות
+ צעד 4/5: בדיקת האבטחה של אומברקו
+ צעד 5/5: אומברקו מוכנה להתחיל
+ תודה שבחרת באומברקו
+ Browse your new site
+You installed Runway, so why not see how your new website looks.]]>
+ Further help and information
+Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]>
+ אומברקו %0% מותקנת ומוכנה לשימוש
+ /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]>
+ started instantly by clicking the "Launch Umbraco" button below. If you are new to Umbraco,
+you can find plenty of resources on our getting started pages.]]>
+ Launch Umbraco
+To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]>
+ ההתחברות לבסיס הנתונים נכשלה.
+ Umbraco גירסה 3
+ Umbraco גירסה 4
+ צפה
+ Umbraco %0% for a fresh install or upgrading from version 3.0.
+
]]>
+ [%0%] התראות %1% בוצעו ב %2%
+ התראות
+
+
+
+ ויש לבחור את החבילה הרצויה. לחבילות Umbraco יש בד"כ יש סיומות בשם ".umb" או ".zip".
+ ]]>
+ יוצר החבילה
+ הדגמה
+ תיעוד
+ מטה דטה עבור החבילה
+ שם החבילה
+ החבילה לא מכילה אף פריט
+
+ ניתן למחוק בבטיחות רבה את החבילה מהמערכת על ידי לחיצה על "הסר חבילה".]]>
+ אין עידכונים זמינים
+ אפשרויות חבילה
+ תיאור החבילה
+ מאגר חבילות
+ אשר הסרה
+ החבילה הוסרה
+ החבילה הוסרה בהצלחה!
+ הסר חבילה
+
+ הערה:כל מסמך, מדיה וכו' התלוים בפריטים שהסרת יפסיקו לעבוד, ויכולים להביא למצב של אי יציבות למערכת,
+ יש למחוק קבצים עם זהירות יתרה, אם יש ספק יש לפנות ליוצר החבילה.]]>
+ הורד עידכון מהמאגר
+ שידרוג חבילה
+ הורדות שידרוג
+ קיים עידכון זמין עבור חבילה זו. ניתן להוריד אותו ישירות ממאגר החבילות של אומברקו.
+ גירסת החבילה
+ צפה באתר החבילה
+
+
+ שמור עיצוב בהדבקה (לא מומלץ)
+ הטקסט שאתה עומד להדביק מכיל עיצוב או תווים מיוחדים. דבר זה יגול להגרם בעת העתקה ממסמך בוורד. אומברקו יכולה להסיר את העיצוב או תווים מיוחדים על מנת שהטקסט המועתק יתאים ל- Web.
+ הסר עיצוב באופן מלא בהדבקה
+ הדבק אך הסר רק עיצוב (מומלץ)
+
+
+ תפקיד בסיסי בהגנה
+ על ידי שימוש בקבוצות הקיימות ב Umbraco.]]>
+ בזיהוי תפקיד המשתמש.]]>
+ עמוד שגיאות
+ השתמש בעת התחברות משתמשים וללא אפשרות גישה
+ בחר איך להגביל את הגישה לעמוד זה
+ %0% כעת מוגן
+ הגנה הוסרה עבור %0%
+ עמוד התחברות
+ בחר את העמוד המכיל טופס התחברות
+ הסר הגנה
+ בחר את העמודים המכילים פרטי התחברות והודעות שגיאה
+ בחר את הכללים אשר יש להם גישה לעמוד זה
+ הגדר שם משתמש וסיסמה עבור עמוד זה
+ הגנת משתמש יחיד
+ אם ברצונך להגדיר הגנה פשוטה בעזרת שימוש בשם משתמש וסיסמה
+
+
+
+ כלול עמודי ילדים שלא פורסמו
+ אנא המתן - הפירסום בתהליך...
+ %0% מתוך %1% עמודים פורסמם בהצלחה...
+ העמוד %0% פורסם.
+ העמוד %0% וכל תתי העמודים פורסמו
+ פרסם את העמוד %0% ואת כל תתי העמודים
+ ok כדי לפרסם %0% ולהפוך תוכן זה זמין לציבור הרחב
+ הינך יכולה לפרסם את כל תתי העמודים על ידי סימון פרסם את העמוד את כל תתי העמודים למטה.
+ ]]>
+
+
+ הוסף קישור חיצוני
+ הוסף קישור פנימי
+ הוסף
+ כותרת
+ עמוד פנימי
+ קישור
+ הורד למטה
+ העלה למעלה
+ פתח בחלון חדש
+ הסר קישור
+
+
+ גירסה עדכנית
+ טקסט אדום לא יוצג בגרסא שנבחרה, טקסט ירוק מייצט טקסט שנוסף.]]>
+ המסמך שוחזר בהצלחה
+ להלן הגרסא שנבחרה כHTML, אם הינך לצפות בשינויים בין שתי הגרסאות בו זמנית, בחר ב diff
+ חזור לאחור אל
+ בחר גירסה
+ תצוגה
+
+
+ ערוך קובץ סקריפט
+
+
+ Concierge
+ תוכן
+ Courier
+ מפתח
+ אשף הגדרת אומברקו
+ מדיה
+ חברים
+ עיתון
+ הגדרות
+ סטטיסטיקות
+ תירגום
+ משתמשים
+
+
+ תבנית ברירת מחדל
+ מפתח מילון
+ על מנת לייבא סוג מסמך,מצא את הקובץ ".udt" במחשב שלך על ידי לחיצה על 'סייר' ואז 'ייבא' (ייתכן ותצטרך לבצע אימות במסך הבא)
+ כותרת לשונית חדשה
+ סוג פריט תוכן
+ סוג
+ גליונות סגנון
+ תכונות גליונות סגנון
+ לשונית
+ כותרת לשונית
+ לשוניות
+
+
+ Sort order
+ Creation date
+ המיון הושלם.
+ יש לגרור את הפריטים מעלה או מטה כדי להגדיר את סדר התוכן. או לחץ על כותרת העמודה כדי למיין את כל פריטי התוכן
+ נא לא לסגור את החלון בזמן המיון]]>
+
+
+ הפירסום בוטל על ידי תוסף צד שלישי
+ סוג תכונה כבר קיים
+ סוג תכונה נשמר
+ סוג מידע: %1%]]>
+ סוג תכונה נמחק
+ סוג מסמך נשמר
+ נוצרה לשונית
+ לשונית נמחקה
+ לשונית עם מזהה: %0% נמחקה
+ סגנון עיצוב לא נשמר
+ סגנון עיצוב נשמר
+ סגנון עיצוב נשמר ללא שגיאות
+ סוג מידע נשמר
+ פריט במילון נשמר
+ הפירסום נכשל, עמוד האב לא מפורסם
+ התוכן פורסם
+ ומוצג לצפיה באתר
+ תוכן נשמר
+ זכור לפרסם את התוכן על מנת שהשינויים יוצגו
+ נשלח לאישור
+ השינויים נשלחו לאישור
+ חבר נשמר
+ תגונה של סגנון עיצוב נשמרה
+ סגנון עיצוב נשמר
+ תבנית נשמרה
+ שגיאה בעת שמירת משתמש (בדוק Log)
+ הגדרות משתמש נשמרו
+ קובץ לא נשמר
+ אין אפשרות לשמור את הקובץ, בדוק הרשאות
+ הקובץ נשמר
+ הקובץ נשמר ללא שגיאות
+ שפה נשמרה
+ Python script לא נשמרו
+ Python script לא נשמרו עקב שגיאות
+ Python script נשמר
+ לא נמצאו שגיאות ב- python script
+ התבנית לא נשמרה
+ שים לב שאין 2 תבניות עם אותו השם/כינוי
+ התבנית נשמרה
+ התבנית נשמרה ללא שגיאות!
+ הקובץ XSLT לאנשמר
+ הקובץ XSLT מכיל שגיאה
+ אין אפשרות לשמור את ה- XSLT, בדוק הרשאות קובץ לפני
+ הקובץ XSLT נשמר
+ אין שגיאות ב- XSLT
+
+
+ השתמש בסינטקס CSS לדוגמא: h1, .redHeader, .blueTex
+ ערוך סגנון עיצוב
+ ערוך הגדרות סגנון עיצוב
+ שם לזיהוי הגדרות ה- style בעורך הטקסט העשיר
+ תצוגה מקדימה
+ עיצובים
+
+
+ ערוך תבנית
+ הוסף איזור תוכן
+ הוסף content area placeholder
+ הוסף פריט מילון
+ הוסף מקרו
+ הוסף שדה עמוד לאומברקו
+ תבנית ראשית
+ מדריך מהיר עבור תבנית תגיות באומברקו
+ תבנית
+
+
+ Choose type of content
+ Choose a layout
+ Add a row
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Click to embed
+ Click to insert image
+ Image caption...
+ Write here...
+
+ Grid Layouts
+ Layouts are the overall work area for the grid editor, usually you only need one or two different layouts
+ Add Grid Layout
+ Adjust the layout by setting column widths and adding additional sections
+ Row configurations
+ Rows are predefined cells arranged horizontally
+ Add row configuration
+ Adjust the row by setting cell widths and adding additional cells
+
+ Columns
+ Total combined number of columns in the grid layout
+
+ Settings
+ Configure what settings editors can change
+
+ Styles
+ Configure what styling editors can change
+
+ Settings will only save if the entered json configuration is valid
+
+ Allow all editors
+ Allow all row configurations
+
+
+ שדה אלטרנטיבי
+ טקסט חלופי
+ מסגרת
+ בחר שדה
+ המרת מעברי שורה
+ החלף מעברי שורה עם תגית ה HTML <br>
+ כן, תאריך בלבד
+ פורמט תאריך
+ קידוד HTML
+ תווים מיוחדים יוחלפו בתווי HTML מתאימים.
+ יוכנס אחרי ערך השדה
+ יוכנס לפני ערך השדה
+ אותיות קטנות
+ ללא
+ הוסף אחרי השדה
+ הוסף לפני השדה
+ רקורסיבי
+ הסר תגי פסקה
+ מסיר את כל ה- <P> בתחילת ובסוף הטקסט
+ אותיות גדולות
+ קידוד URL
+ תווים מיוחדים יעוצבו ב- URL
+ יבוצע שימוש אך ורק עם ערך השדה יהיה ריק
+ בשדה זה יבוצע שימוש אך ורק אם השדה העיקרי יהיה ריק
+ כן, עם שעה. תו מפריד:
+
+
+ משימות משוייכות אליך
+ שמשוייכות אלייך. כדי לצפות בפרטים נוספים הכוללים תגובות, יש ללחוץ על "פרטים" או על שם העמוד.
+ ניתן בנוסף להוריד את הקובץ כ XML ישירות למחשב על ידי לחיצה ב"הורד קובץ XML".
+ כדי לסגור את משימת התרגום, אנא בחר בצפיה נרחבת ואז יש ללחוץ על כפתור "סגור משימה".
+ ]]>
+ סגור משימה
+ פרטי תירגום
+ הורד את כל התירגומים כקובץ xml
+ הורד קובץ xml
+ הורד xml DTD
+ שדות
+ כלול דפי משנה
+
+ [%0%] משימות תירגום עבור %1%
+ לא נמצאו משתמשמים המוגדרים כמתרגמים. יש ליצור משתמש המוגדר כמתרגם לפני שליחת תוכן לתירגום
+ משימות שנוצרו על ידך
+ שנוצרו על ידיך. כדי לצפות בפרטים נוספים הכוללים תגובות,
+ יש ללחוץ על "פרטי תרגום" או על ידי שם העמוד. ניתן בנוסף להוריד את העמוד כקובץ XML ישירות למחשב על ידי לחיצה על "הורד קובץ XML".
+ כדי לסגור את משימת התרגום, אנא בחר בצפיה נרחבת ואז יש ללחוץ על כפתור "סגור משימה".
+ ]]>
+ העמוד '%0%' נשלח לתירגום
+ שלח את העמוד '%0%' לתירגום
+ הוקצה על ידי
+ משימה נפתחה
+ סך הכל מילים
+ תרגם עבור
+ התירגום הושלם.
+ ניתן לראות תצוגה מקדימה של העמודים שכבר תורגמו על ידי לחיצה על הלינק למטה. If the original page is found, you will get a comparison of the 2 pages.
+ התירגום נכשל, קובץ ה- xml עלול להיות מקולקל
+ אפשרויות תירגום
+ מתרגם
+ העלה קובץ תירגום ב xml
+
+
+ זיכרון מטמון בדפדפן
+ סל מיחזור
+ יצירת חבילות
+ סוגי מידע
+ מילון
+ חבילות מותקנות
+ התקן עיצוב
+ התקן ערכת התחלה
+ שפות
+ התקן חבילה מקומית
+ מקרו
+ סוגי מדיה
+ משתמשים
+ קבוצות משתמשים
+ כללים
+ סוגי משתמשים
+ סוגי מסמכים
+ חבילות
+ חבילות
+ קבצי פייתון
+ התקן מתוך מאגר
+ התקן Runway
+ מודולי Runway
+ קבצי סקריפטים
+ סקריפטים
+ גיליונות סגנון
+ תבניות
+ קבצי XSLT
+
+
+ עידכון חדש זמין
+ %0% זמין, כאן להורדה
+ אין תקשורת עם השרת
+ בדיקת עידכונים נכשלה. בדוק את ה trace-stack למידע נוסף
+
+
+ מנהל ראשי
+ שדה קטגוריה
+ שנה את הסיסמה שלך
+ בעמוד זה ניתן לשנות את הסיסמה שלך ולאחר מכן ללחוץ על הכפתור 'שנה סיסמה' למטה
+ ערוץ תוכן
+ שדה תיאור
+ נטרל משתמש
+ שדה מסמך
+ עורך
+ שדה תקציר
+ שפה
+ שם משתמש
+ התחלה בפריט המדיה
+ אזורים
+ נטרל גישת אומברקו
+ סיסמה
+ הסיסמה שונתה בהצלחה!
+ אישור סיסמה
+ הזן את הסיסמה החדשה שלך
+ שדה סיסמה לא יכול להיות ריק !
+ סיסמאות לא תואמות, אנא בדוק זאת ונסה שנית
+ אישור סיסמה לא תואם !
+ החלף הרשאות בכל תתי הפריטים
+ הינך משנה הרשאות לעמודים הבאים:
+ בחר עמוד/עמודים לשינוי הרשאות
+ חפש בכל הפריטים
+ התחלה בפריט התוכן
+ שם תצוגה
+ הרשאות משתמש
+ סוג משתמש
+ סוגי משתמש
+ כותב
+
+
diff --git a/WebCms/Umbraco/Config/Lang/it.xml b/WebCms/Umbraco/Config/Lang/it.xml
new file mode 100644
index 0000000..9cd916d
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/it.xml
@@ -0,0 +1,924 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Gestisci hostnames
+ Audit Trail
+ Sfoglia
+ Copia
+ Crea
+ Crea pacchetto
+ Cancella
+ Disabilita
+ Svuota il cestino
+ Esporta il tipo di documento
+ Importa il tipo di documento
+ Importa il pacchetto
+ Modifica in Area di Lavoro
+ Uscita
+ Sposta
+ Notifiche
+ Accesso pubblico
+ Pubblica
+ Aggiorna nodi
+ Ripubblica intero sito
+ Permessi
+ Annulla ultima modifica
+ Invia per la pubblicazione
+ Invia per la traduzione
+ Ordina
+ Invia la pubblicazione
+ Traduci
+ Annulla pubblicazione
+ Aggiorna
+
+
+ Aggiungi nuovo dominio
+ Dominio
+
+
+
+
+
+ Hostname non valido
+ Modifica il dominio corrente
+
+
+ Visualizzazione per
+
+
+ Grassetto
+ Cancella rientro paragrafo
+ Inserisci dal file
+ Inserisci intestazione grafica
+ Modifica Html
+ Inserisci rientro paragrafo
+ Corsivo
+ Centra
+ Allinea testo a sinistra
+ Allinea testo a destra
+ Inserisci Link
+ Inserisci local link (ancora)
+ Elenco puntato
+ Elenco numerato
+ Inserisci macro
+ Inserisci immagine
+ Modifica relazioni
+ Salva
+ Salva e pubblica
+ Salva e invia per approvazione
+ Anteprima
+
+ Scegli lo stile
+ Mostra gli stili
+ Inserisci tabella
+
+
+ Informazioni su questa pagina
+ Link alternativo
+
+ Links alternativi
+ Clicca per modificare questo elemento
+ Creato da
+ Creato il
+ Tipo di documento
+ Modifica
+ Attivo fino al
+
+
+ Ultima pubblicazione
+ Link ai media
+ Tipo di media
+ Gruppo di membri
+ Ruolo
+ Tipologia Membro
+
+ Titolo della Pagina
+
+
+ Pubblicato
+ Stato della pubblicazione
+ Pubblicato il
+ Rimuovi data
+ Ordinamento dei nodi aggiornato
+
+ Statistiche
+ Titolo (opzionale)
+ Tipo
+ Non pubblicare
+ Ultima modifica
+ Rimuovi il file
+ Link al documento
+
+
+
+ Crea al
+ Scegli il tipo ed il titolo
+
+
+
+
+
+ hai aperto una nuova finestra
+ Riavvia
+ Visita
+ Benvenuto
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Nome
+ Gestione alias Hostnames
+ Chiudi questa finestra
+
+
+
+
+
+ Taglia
+ Modifica elemento Dictionary
+ Modifica il linguaggio
+ Inserisci il link locale
+ Inserisci carattere
+
+
+ Inserisci link
+ Inserisci macro
+ Inserisci tabella
+ Ultima modifica
+ Link
+
+
+
+ Impostazioni Macro
+
+ Incolla
+ Modifica il Permesso per
+
+
+
+ regexlib.com ha attualmente qualche problema, di cui non abbiamo il controllo. Siamo spiacevoli dell'inconveniente.]]>
+
+ Elimina Macro
+ Campo obbligatorio
+
+
+
+ Numero di colonne
+ Numero di righe
+ Imposta identificativo del placeholder. Settando tale ID puoi inserire il contenuto in questo template dai template figli, facendo riferimento a questo ID usando il codice <asp:content />.]]>
+ Seleziona Identificativo del placeholder dalla lista seguente. Puoi scegliere solo l'ID appartenente al template master corrente.]]>
+
+ Seleziona elemento
+ Visualizza gli elementi in cache
+
+
+
+
+
+
+ Possibili nodetypes figli
+ Crea
+ Cancella tab
+ Descrizione
+ Nuova tab
+ Tab
+ Miniatura
+
+
+
+
+
+ Rendering controllo
+ Bottoni
+ Abilita impostazioni avanzate per
+ Abilita menu contestuale
+ Dimensione massima delle immagini inserite
+ Fogli di stile collegati
+ Visualizza etichetta
+ Larghezza e altezza
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Info
+ Azione
+ Aggiungi
+ Alias
+
+ Bordo
+ o
+ Annulla
+
+ Scegli
+ Chiudi
+ Chiudi la finestra
+ Commento
+ Conferma
+ Blocca le proporzioni
+ Continua
+ Copia
+ Crea
+ Base di dati
+ Data
+ Default
+ Elimina
+ Eliminato
+ Elimina...
+ Design
+ Dimensioni
+
+ Scarica
+ Modifica
+ Modificato
+ Elementi
+ Email
+ Errore
+ Trova
+ Cartella
+ Altezza
+ Guida
+ Icona
+ Importa
+
+ Inserisci
+ Installa
+ Giustificato
+ Lingua
+ Layout
+ Caricamento
+ Bloccato
+ Login
+ Log off
+ Logout
+ Macro
+ Sposta
+ Nome
+ Nuovo
+ Successivo
+ No
+ di
+ Ok
+ Apri
+ o
+ Password
+ Percorso
+
+
+ Precedente
+
+
+ Cestino
+ Rimangono
+ Rinomina
+ Rinnova
+ Riprova
+ Permessi
+ Cerca
+ Server
+ Mostra
+ Mostra la pagina inviata
+ Dimensione
+ Ordina
+ Submit
+ Tipo
+
+ Su
+ Aggiorna
+ Aggiornamento
+ Carica
+ Url
+ Utente
+
+ Valore
+ Vedi
+ Benvenuto...
+ Larghezza
+ Si
+ Reorder
+ I am done reordering
+
+
+ Colore di sfondo
+ Grassetto
+ Colore del testo
+ Carattere
+ Testo
+
+
+ Pagina
+
+
+
+
+
+
+ installa per installare il database Umbraco %0% ]]>
+ Avanti per proseguire.]]>
+
+ Database non trovato! Perfavore, controlla che le informazioni della stringa di connessione nel file "web.config" siano corrette.
+
Per procedere, edita il file "web.config" (utilizzando Visual Studio o l'editor di testo che preferisci), scorri in basso, aggiungi la stringa di connessione per il database chiamato "umbracoDbDSN" e salva il file.
]]>
+
+
+ Premi il tasto aggiorna per aggiornare il database ad Umbraco %0%
Non preoccuparti, il contenuto non verrà perso e tutto continuerà a funzionare dopo l'aggiornamento!
]]>
+ Premi il tasto Avanti per continuare.]]>
+ Avanti per continuare la configurazione.]]>
+ La password predefinita per l'utente di default deve essere cambiata!]]>
+ L'utente di default è stato disabilitato o non ha accesso ad Umbraco!
Non è necessario eseguire altre operazioni. Clicca il tasto Avanti per continuare.]]>
+ La password è stata modificata con successo
Non è necessario eseguire altre operazioni. Clicca il tasto Avanti per continuare.]]>
+
+ Umbraco crea un utente ('admin')predefinito e password ('default'). E' importante modificare la password con una personale.
Questo passaggio controlla la password predefinita e ricorda, se necessario, di modificarla.
]]>
+
+
+
+
+
+
+ Le impostazioni dei permessi sono perfette!
Puoi eseguire Umbraco senza problemi, ma potresti non poter installare i pacchetti che sono consigliati per sfruttare tutti i vantaggi offerti da Umbraco.]]>
+
+
+ video tutorial su come impostare i permessi delle cartelle per Umbraco o leggi la versione testuale.]]>
+ Le impostazioni dei permessi potrebbero avere dei problemi!
Puoi eseguire Umbraco, ma potresti non essere in grado di creare cartelle o installare pacchetti che sono raccomandati per sfruttare tutti i vantaggi di Umbraco.]]>
+ Le impostazioni dei permessi non sono corrette per Umbraco!
Per eseguire Umbraco, devi aggiornare le impostazioni dei permessi.]]>
+ La configurazione dei permessi è perfetta!
Sei pronto per avviare Umbraco e installare i pacchetti!]]>
+
+
+
+
+
+ Guarda come) Puoi anche installare eventuali Runway in un secondo momento. Vai nella sezione Developer e scegli Pacchetti.]]>
+
+ Runway è installato
+
+ Questa è la lista dei nostri moduli raccomandati, seleziona quali vorresti installare, o vedi l'intera lista di moduli
+ ]]>
+ Raccommandato solo per utenti esperti
+ Vorrei iniziare da un sito semplice
+
+ "Runway" è un semplice sito web contenente alcuni tipi di documento e alcuni templates di base. L'installer configurerà Runway per te automaticamente,
+ ma tu potrai facilmente modificarlo, estenderlo o eliminarlo. Non è necessario installarlo e potrai usare Umbraco anche senza di esso, ma
+ Runway ti offre le basi e le best practices per cominciare velocemente.
+ Se sceglierai di installare Runway, volendo potrai anche selezionare i moduli di Runway per migliorare le pagine.
+
+
+ Inclusi in Runway: Home page, pagina Guida introduttiva, pagina Installazione moduli
+ Moduli opzionali: Top Navigation, Sitemap, Contatti, Gallery.
+
+ ]]>
+ Cosa è Runway
+ Passo 1/5 Accettazione licenza
+ Passo 2/5: Configurazione database
+ Passo 3/5: Controllo permessi dei file
+ Passo 4/5: Controllo impostazioni sicurezza
+ Passo 5/5: Umbraco è pronto per iniziare
+ Grazie per aver scelto Umbraco
+ Naviga per il tuo nuovo sito
+Hai installato Runway, quindi perché non dare uno sguardo al vostro nuovo sito web.]]>
+ Ulteriori informazioni e assistenza
+Fatti aiutare dalla nostra community, consulta la documentazione o guarda alcuni video gratuiti su come costruire un semplice sito web, come usare i pacchetti e una guida rapida alla terminologia Umbraco]]>
+
+ /web.config e aggiornare la chiave AppSetting UmbracoConfigurationStatus impostando il valore '%0%'.]]>
+
+ iniziare immediatamente cliccando sul bottone "Avvia Umbraco". Se sei nuovo a Umbraco, si possono trovare un sacco di risorse sulle nostre pagine Getting Started.]]>
+
+ Avvia Umbraco
+Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e iniziare ad aggiungere i contenuti, aggiornando i modelli e i fogli di stile o aggiungere nuove funzionalità]]>
+ Connessione al database non riuscita.
+ Umbraco Versione 3
+ Umbraco Versione 4
+ Guarda
+ Umbraco %0% per una nuova installazione o per l'aggiornamento dalla versione 3.0.
+
]]>
+ [%0%] Notifica per %1% eseguita su %2%
+ Notifiche
+
+
+
+ e selezionando il pacchetto. I pacchetti Umbraco generalmente hanno l'estensione ".umb" o ".zip".
+ ]]>
+ Autore
+ Dimostrazione
+ Documentazione
+ Meta dati pacchetto
+ Nome del pacchetto
+ Il pacchetto non contiene tutti gli elementi
+
+ E' possibile rimuovere questo pacchetto dal sistema cliccando "rimuovi pacchetto" in basso.]]>
+ Non ci sono aggiornamenti disponibili
+ Opzioni pacchetto
+ Pacchetto leggimi
+ Pacchetto repository
+ Conferma eliminazione
+
+
+ Disinstalla pacchetto
+
+ Avviso: tutti i documenti, i media, etc a seconda degli elementi che rimuoverai, smetteranno di funzionare, e potrebbero portare a un'instabilità del sistema,
+ perciò disinstalla con cautela. In caso di dubbio contattare l'autore del pacchetto.]]>
+
+ Aggiorna pacchetto
+
+
+ Versione del pacchetto
+ Vedi sito web pacchetto
+
+
+
+
+
+
+
+
+
+ usando i gruppi di membri di Umbraco.]]>
+ l'autenticazione basata sui ruoli.]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ok per pubblicare %0% e rendere questo contenuto accessibile al pubblico.
Puoi pubblicare questa pagina e tutte le sue sottopagine selezionando pubblica tutti i figli qui sotto.]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Il testo in rosso non verrà mostrato nella versione selezionata, quello in verde verrà aggiunto]]>
+
+
+
+
+
+
+
+
+
+
+ Concierge
+ Contenuto
+ Courier
+ Sviluppo
+ Configurazione guidata Umbraco
+ Media
+ Membri
+ Newsletters
+ Impostazioni
+ Statistiche
+ Traduzione
+ Utenti
+
+
+
+ Tipo di contenuto master abilitato
+ Questo tipo di contenuto usa
+
+
+
+
+
+ Tipo
+ Foglio di stile
+
+ Tab
+ Titolo tab
+ Tabs
+
+
+ Sort order
+ Creation date
+
+
+ Non chiudere questa finestra durante l'operazione]]>
+
+
+
+
+
+ Tipo di dati: %1%]]>
+
+ Tipo di documento salvato
+ Tab creata
+ Tab eliminata
+ Tab con id: %0% eliminata
+ Contenuto non pubblicare
+
+
+
+ Tipo di dato salvato
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tipo utente salvato
+
+
+
+
+
+ Partial view salvata
+ Partial view salvata senza errori!
+ Partial view non salvata
+ Errore durante il salvataggio del file.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Anteprima
+ Stili
+
+
+
+
+
+
+
+
+ Master template
+
+ Template
+
+
+ Choose type of content
+ Choose a layout
+ Add a row
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Click to embed
+ Click to insert image
+ Image caption...
+ Write here...
+
+ Grid Layouts
+ Layouts are the overall work area for the grid editor, usually you only need one or two different layouts
+ Add Grid Layout
+ Adjust the layout by setting column widths and adding additional sections
+ Row configurations
+ Rows are predefined cells arranged horizontally
+ Add row configuration
+ Adjust the row by setting cell widths and adding additional cells
+
+ Columns
+ Total combined number of columns in the grid layout
+
+ Settings
+ Configure what settings editors can change
+
+ Styles
+ Configure what styling editors can change
+
+ Settings will only save if the entered json configuration is valid
+
+ Allow all editors
+ Allow all row configurations
+
+
+
+
+
+ Scegli il campo
+ Converte le interruzioni di linea
+
+ Campi Personalizzati
+
+
+
+
+
+
+ Minuscolo
+ Nessuno
+
+
+ Ricorsivo
+
+
+ Campi Standard
+ Maiuscolo
+
+
+
+
+
+
+
+ Task assegnati a te
+ assegnate a te. Per vedere i dettagli, commenti inclusi, clicca su "Dettagli" o sul nome della pagina.
+ Puoi anche scaricare la pagina direttamente in formato XML cliccando sul link "Scarica Xml".
+ Per chiudere una traduzione, vai sulla vista Dettagli e clicca sul pulsante "Chiudi".
+ ]]>
+ chiudi task
+ Dettagli
+ Scarica tutte le traduzioni in formato xml
+ Scarica xml
+ Scarica xml DTD
+ Campi
+ Includi le sottopagine
+
+ [%0%] Traduzione per %1%
+
+
+ create da te. Per vedere i dettagli, inclusi i commenti,
+ clicca su "Dettagli" o sul nome della pagina. Puoi anche scaricare la pagina direttamente in formato XML cliccando sul link "Scarica Xml".
+ Per chiudere una traduzione, vai sulla vista Dettagli e clicca sul pulsante "Chiudi".
+ ]]>
+
+
+
+
+
+
+
+
+
+
+ Traduttore
+
+
+
+ Cache Browser
+ Cestino
+ Pacchetti creati
+ Tipi di dato
+ Dizionario
+ Pacchetti installati
+ Installare skin
+ Installare starter kit
+ Lingue
+ Installa un pacchetto locale
+ Macros
+ Tipi di media
+ Membri
+ Gruppi di Membri
+ Ruoli
+ Tipologia Membri
+ Tipi di documento
+ Pacchetti
+ Pacchetti
+ Files Python
+ Installa dal repository
+ Installa Runway
+ Moduli Runway
+ Files di scripting
+ Scripts
+ Fogli di stile
+ Templates
+ Files XSLT
+
+
+
+
+
+
+
+
+ Amministratore
+ Campo Categoria
+ Cambia la tua password
+
+ Conferma la nuova password
+ Contenuto del canale
+ Campo Descrizione
+ Disabilita l'utente
+ Tipo di Documento
+ Editor
+ Campo Eccezione
+ Lingua
+ Login
+
+ Sezioni
+ Modifica la tua password
+
+ Password
+
+
+ Password attuale
+
+
+
+
+
+
+
+
+
+
+ Username
+
+
+
+ Writer
+
+
diff --git a/WebCms/Umbraco/Config/Lang/ja.xml b/WebCms/Umbraco/Config/Lang/ja.xml
new file mode 100644
index 0000000..4f84944
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/ja.xml
@@ -0,0 +1,1269 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ ドメインの割り当て
+ 動作記録
+ ノードの参照
+ ドキュメントタイプの変更
+ コピー
+ 新規作成
+ パッケージの作成
+ 削除
+ 無効
+ ごみ箱を空にする
+ ドキュメントタイプの書出
+ ドキュメントタイプの読込
+ パッケージの読み込み
+ ライブ編集
+ ログアウト
+ 移動
+ メール通知
+ 一般公開
+ 公開
+ 公開を止める
+ 最新の情報に更新
+ サイトのリフレッシュ
+ 復元
+ アクセス権
+ 以前の版に戻る
+ 公開に送る
+ 翻訳に送る
+ 並べ替え
+ 公開する
+ 翻訳
+ 更新
+ 初期値
+
+
+ アクセスが拒否されました
+ ドメインの割り当て
+ ドメインの削除
+ 適当でないノード名
+ 適当でないホスト名
+ そのホスト名は既に利用されています
+ 言語コード
+ ドメイン
+ ドメイン '%0%' が新たに割り当てられました
+ ドメイン '%0%' は削除されました
+ ドメイン '%0%' は既に割り当てられています
+ ドメイン '%0%' は更新されました
+ ドメインの編集
+
+ 継承
+ カルチャの割り当て
+
+ ドメイン割り当て
+
+
+ これらを表示
+
+
+ 選択
+ 現在のフォルダを選択
+ その他のアクション
+ 太字
+ インデント解除
+ フォーム フィールドの挿入
+ グラフィックヘッドラインの挿入
+ HTMLの編集
+ インデント
+ イタリック
+ 中央揃え
+ 左揃え
+ 右揃え
+ リンクの挿入
+ アンカーの挿入
+ 番号なしリスト
+ 番号付きリスト
+ マクロの挿入
+ 画像の挿入
+ 関係性の編集
+ リストに戻る
+ 保存
+ 保存及び公開
+ 保存して承認に送る
+ リスト ビューの保存
+ プレビュー
+ テンプレートが指定されていないのでプレビューは無効になっています
+ スタイルの選択
+ スタイルの表示
+ 表の挿入
+ モデルを生成
+
+
+ ドキュメントタイプを変更するには、まず有効なドキュメントタイプのリストから選択します
+ 確認および現在のドキュメントタイプからのマッピングを割り当て、保存します。
+ コンテントは再公開されています
+ 現在のプロパティ
+ 現在のドキュメントタイプ
+ 有効な代替タイプが存在しないため変更することができません。選択されたコンテントの親の下に許可されたドキュメントタイプへのみ変更ができます
+ ドキュメントタイプを変更しました
+ プロパティを割り当てる
+ 割り当てるプロパティ
+ 新しいテンプレート
+ 新しいドキュメントタイプ
+ なし
+ コンテント
+ ドキュメントタイプを変更する
+ 選択コンテンツのドキュメント タイプは、[新しいタイプ] に変更され、以下のプロパティがマップされました。
+ から
+ 1つ以上のプロパティを割り当てられませんでした。プロパティが定義が重複しています
+ 有効なドキュメントタイプのみが表示されます
+
+
+ 公開されました
+ このページについて
+ エイリアス
+ (画像を電話でわかるように言葉で説明)
+ 別名のリンク
+ クリックでアイテムを編集する
+ 作成者
+ 作成者
+ 更新者
+ 作成日時
+ このドキュメントが作成された日時
+ ドキュメントタイプ
+ 編集中
+ 公開終了日時
+ このページは公開後変更されています
+ このページは公開されていません
+ 公開日時
+ 表示するアイテムはありません
+ リストに表示するアイテムはありません
+ メディアタイプ
+ メディアの項目へのリンク
+ メンバーグループ
+ 役割
+ メンバータイプ
+ 日時が選択されていません
+ タイトル
+ プロパティ
+ このページは公開されましたが、親ページの '%0%' が非公開のため閲覧できません
+ このコンテントは公開されていますがキャッシュされていません(内部エラー)
+ 公開
+ 公開状態
+ 公開開始日時
+ 公開停止日時
+ 日時の消去
+ 並び順が更新されました
+ ノードをドラッグ、クリック、または列のヘッダーをクリックする事でノードを簡単にソートできます。SHIFT、CONTROLキーを使い複数のノードを選択する事もできます。
+ 統計
+ タイトル (オプション)
+ 代替テキスト (オプション)
+ 型
+ 非公開
+ 最終更新日時
+ このドキュメントが最後に更新された日時
+ ファイルの消去
+ ページへのリンク
+ グループのメンバー
+ グループのメンバーではありません
+ 子コンテンツ
+ ターゲット
+
+
+ クリックしてアップロードする
+ ファイルをここへドロップ..
+ メディアへリンク
+ またはクリックしてファイルを選択
+ 使用可能なファイル タイプ
+ ファイルの最大サイズ
+
+
+ メンバーの新規作成
+ 全てのメンバー
+
+
+ どこに新しい %0% を作りますか
+ ここに作成
+ 型とタイトルを選んでください
+ "document types".]]>
+ "media types".]]>
+ テンプレートなしのドキュメント タイプ
+
+
+ ウェブサイトを参照する
+ 次回から表示しない
+ Umbracoが起動しない時は、このサイトのポップアップを許可してください。
+ 新規ウィンドウで開く
+ 再起動
+ 訪れる
+ ようこそ
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ 名前
+ ドメインの割り当て
+ このウィンドウを閉じる
+ 削除しますか?
+ 無効にしますか?
+ これらの %0% 個の項目を削除する場合は、チェックボックスにチェックを入れてください
+ ログアウトしますか?
+ 本当にいいですか?
+ 切り取り
+ ディクショナリのアイテムの編集
+ 言語の編集
+ アンカーの挿入
+ 文字の挿入
+ ヘッドライン画像の挿入
+ 画像の挿入
+ リンクの挿入
+ クリックするとマクロを追加
+ 表の挿入
+ 最近の更新
+ リンク
+ 内部リンク:
+ 内部リンクを使うときは、リンクの前に "#" を挿入してください。
+ 新規ウィンドウで開きますか?
+ マクロの設定
+ このマクロは編集できるプロパティがありません
+ 貼り付け
+ 許可の編集
+ ごみ箱を空にしています。実行中はウィンドウを閉じないでください。
+ ごみ箱は空です
+ ごみ箱から削除すると復活させることはできません
+ regexlib.comのウェブサービスに現在問題が起きているかもしれず、操作できませんでした。大変申し訳ありませんがこの機能は使用できません。]]>
+ フォームのフィールドを正規表現で検証できます。例: 'email, 'zip-code' 'url'
+ マクロを削除
+ 必須フィールド
+ サイトは再インデックスされました
+ ウェブサイトのキャッシュがリフレッシュされました。 公開されているコンテンツはリフレッシュされましたが、非公開のコンテンツは非公開のままです。
+ ウェブサイトのキャッシュがリフレッシュされます。 公開されているコンテンツはリフレッシュされますが、非公開のコンテンツは非公開のままです。
+ 列数
+ 行数
+ プレースホルダにidを設定して、子テンプレートからもこのテンプレートをコンテントに入れられるようにできます。
+ IDは<asp:content />エレメントとして用います。]]>
+ プレースホルダのidを選択してください。
+ このテンプレートのマスターで使用可能なIdのみ選択可能です。]]>
+ クリックすると画像がフルサイズで表示されます
+ 項目の選択
+ キャッシュされている項目の表示
+ フォルダーの作成...
+
+ オリジナルに関連付ける
+ フレンドリーなコミュニティ
+
+ ページへリンク
+
+ リンク ドキュメントを新しいウィンドウまたはタブで開く
+ リンク ドキュメントをウィンドウ全文表示で開く
+ 親フレームでリンク ドキュメントを開く
+
+ メディアへリンク
+
+ メディアの選択
+ アイコンの選択
+ アイテムの選択
+ リンクの選択
+ マクロの選択
+ コンテンツの選択
+ メンバーの選択
+ メンバー グループの選択
+
+ このマクロのパラメーターはありません
+
+ 外部ログイン プロバイダー
+ 例外の詳細
+ スタックトレース
+ Inner Exception
+
+ 次をリンク:
+ 次をリンク解除:
+
+ アカウント
+
+ エディターの選択
+
+
+ %0%' の別の言語版を編集するには、左側のメニューの'言語'でその言語を追加します
+ ]]>
+ カルチャ名
+
+
+ ユーザー名を入力...
+ パスワードを入力...
+ %0%と命名します...
+ ここに名称を入力してください...
+ ラベル...
+ 説明を入力してください...
+ 検索する...
+ 条件で絞り込む...
+ 入力してタグを追加 (各タグの後に Enter を押してください)...
+
+
+ ルートノードとして許可する
+ これを有効にするとコンテンツとメディアツリーのルートレベルに作成することができます
+ 子ノードとして許可するタイプ
+ ドキュメント タイプ構成
+ 新規作成
+ タブの削除
+ 説明
+ 新しいタブ
+ タブ
+ サムネイル
+ リストビューを有効にする
+ 子ノードをツリーに表示せずにリストビューに表示します
+ 現在のリストビュー
+ 有効なリストビューデータタイプ
+ カスタムリストビューを作成する
+ カスタムリストビューを削除する
+
+
+ 値の前に追加
+ データベースのデータ型
+ プロパティ エディター GUID
+ プロパティ エディター
+ ボタン
+ 高度な設定を有効にする
+ コンテキストメニューを有効にする
+ 挿入される画像のデフォルト最大サイズ
+ 関連付けるスタイルシート
+ ラベルの表示
+ 幅と高さ
+
+
+ データは保存されましたが、公開前にこのページの幾つかのエラーを修正しなければなりません:
+ 現在のメンバーシッププロバイダではパスワードを変更できません (EnablePasswordRetrievalがtrueでなければなりません)
+ %0% は既にあります
+ 次の様なエラーが発生しています:
+ 次の様なエラーが発生しています:
+ パスワードは最低でも %0% 文字の長さかつ %1% 文字以上の数以外の文字を含める必要があります
+ %0% は整数でなければなりません
+ %1% タブの %0% フィールドは必須です
+ %0% は必須です
+ %1% の %0% は正しい書式ではありません
+ %0% は正しい書式ではありません
+
+
+ サーバー エラーが発生しました
+ 指定されたファイルタイプは管理者のみに許可されます
+ 注意! CodeMirrorが設定で有効化されていますが、 Internet Explorerでは不安定なので無効化してください。
+ 新しいプロパティ型のエイリアスと名前の両方を設定してください!
+ 特定のファイルまたはフォルタの読み込み/書き込みアクセスに問題があります
+ Partial View スクリプトの読み込みエラー (ファイル: %0%)
+ userControl の読み込みエラー '%0%'
+ customControl の読み込みエラー (アセンブリ: %0%, タイプ: '%1%')
+ MacroEngine スクリプトの読み込みエラー (ファイル: %0%)
+ XSLT ファイル解析エラー: %0%
+ XSLT ファイル読み込みエラー: %0%
+ タイトルを入力してください
+ 型を選択してください
+ 元画像より大きくしようとしていますが、本当によろしいのですか?
+ Pythonスクリプトにエラーがあります
+ 1つ以上のエラーがあるのでこのPythonスクリプトは保存できませんでした
+ 開始ノードが削除されています。管理者に連絡してください。
+ スタイルを変更する前にコンテントをマークしてください
+ 有効なスタイルがありません
+ 結合したい2つのセルの左側にカーソルを置いてください
+ このセルは結合されたものではないので分離する事はできません。
+ XSLTソースにエラーがあります
+ 1つ以上のエラーがあるのでこのXSLTは保存できませんでした
+ このプロパティに使用されているデータタイプにエラーがあります
+
+
+ Umbracoについて
+ アクション
+ アクション選択
+ 追加
+ エイリアス
+ すべて
+ 確かですか?
+ 枠線
+ または
+ キャンセル
+ セルの余白
+ 選択
+ 閉じる
+ ウィンドウを閉じる
+ コメント
+ 確認
+ 縦横比
+ 続行
+ コピー
+ 新規作成
+ データベース
+ 日付
+ 既定
+ 削除
+ 削除済
+ 削除中...
+ デザイン
+ 次元
+ 下
+ ダウンロード
+ 編集
+ 編集済
+ 要素
+ 電子メール
+ エラー
+ 検索
+ 高さ
+ ヘルプ
+ アイコン
+ インポート
+ 内側の余白
+ 挿入
+ インストール
+ 無効
+ 位置揃え
+ 言語
+ レイアウト
+ 読み込み中
+ ロックされています
+ ログイン
+ ログオフ
+ ログアウト
+ マクロ
+ 移動
+ もっと
+ 名前
+ 新規
+ 次へ
+ いいえ
+ of
+ OK
+ 開く
+ または
+ パスワード
+ パス
+ プレースホルダのID
+ しばらくお待ちください...
+ 前へ
+ プロパティ
+ フォームからEmailを受信
+ ごみ箱
+ 残り
+ 名前の変更
+ 更新
+ この項目は必須です
+ 再試行
+ 許可
+ 検索
+ サーバー
+ 表示
+ 送信後にページを表示
+ サイズ
+ 並べ替え
+ 送信
+ 型
+ 検索...
+ 上
+ 更新
+ アップグレード
+ アップロード
+ URL
+ ユーザー
+ ユーザー名
+ 値
+ ビュー
+ ようこそ...
+ 幅
+ はい
+ フォルダー
+ 検索結果
+ 順序変更
+ 順序変更終了
+ プレビュー
+ パスワードの変更
+ ->
+ リスト ビュー
+ 保存...
+ 現在
+ 移動
+ 埋め込み
+
+
+
+ ブラック
+ グリーン
+ イエロー
+ オレンジ
+ ブルー
+ レッド
+
+
+
+ タブの追加
+ プロパティの追加
+ エディターの追加
+ テンプレートの追加
+ 子ノードの追加
+ 子の追加
+
+ データ タイプの編集
+
+ セクションの移動
+
+ ショートカット
+ ショートカットの表示
+
+ リスト ビューの切り替え
+ ルートとして許可に切り替え
+
+
+
+ 背景色
+ 太字
+ テキストの色
+ フォント
+ テキスト
+
+
+ ページ
+
+
+ インストーラーはデータベースに接続できませんでした。
+ web.configファイルを保存できませんでした。接続文字列を手作業で編集してください。
+ データベースが見つかりました。識別子:
+ データベースの設定
+ インストールボタンを押すと Umbraco %0% のデータベースへインストールします
+ ]]>
+ 次へを押して続行してください。]]>
+ データベースを見つけられません!"web.config"ファイルの中の"接続文字列"を確認してください。
+
]]>
+
+ 필요하시다면 사용하시는 ISP쪽에 문의하시기 바랍니다..
+ 로컬 머신이나 서버에 설치되어 있다면 해당 시스템 관리자에게 문의하시기 바랍니다.]]>
+ 업그레이드 버튼을 누르면 여러분의 데이터베이스를 Umbraco %0% 로 업데이트합니다.
어떤 컨텐트도 삭제되지 않으니 걱정마세요!
]]>
+ 계속 진행하시려면 다음 을 누르세요. ]]>
+ 다음을 클릭하시면 설정마법사를 계속 진행합니다.]]>
+ 기본 사용자의 암호가 변경되어야 합니다!]]>
+ 기본 사용자가 비활성화되었거나 Umbraco에 접근할 수 없습니다!
더 이상 과정이 필요없으시면 다음을 눌러주세요.]]>
+ 설치후 기본사용자의 암호가 성공적으로 변경되었습니다!
더 이상 과정이 필요없으시면 다음을 눌러주세요.]]>
+ 비밀번호가 변경되었습니다!
+
+ Umbraco 는 로긴을 위한 기본 ('admin') 사용자를 만들고 암호를 ('default')로 설정합니다. 암호를 꼭변경하셔야 합니다.
+
+
+ 이 과정은 기본 사용자의 암호를 체크하고 변경이 필요한 부분을 제안합니다.
+
+ ]]>
+ 편리한 시작을 위해, 소개 Video를 시청하세요
+ 다음버튼을 누르시면 (또는Web.config에 UmbracoConfigurationStatus를 수정하시면), 여러분은 아래에 명시된 소프트웨어 라이센스를 수락합니다. Umbraco 배포는 2가지 다른 라이센스로 구성되어 있습니다. 프레임워크에는 오픈소스 MIT라이센스가 UI에는 Umbraco 프리웨어 라이센스가 적용됩니다.
+ 아직 설치되지 않았습니다.
+ 영향받는 파일과 폴더
+ Umbraco권한관리을 위해 더정보가 필요하시면 여기를 누르세요
+ 다음 파일/폴더에 ASP.NET 수정권한이 필요합니다.
+ 권한 설정이 대부분 완벽합니다!
+ 여러분은 문제없이 Umbraco사용이 가능하지만 일부 추천 패키지가 설치되지 않을 수 있습니다.]]>
+ 문제해결방법
+ 문서버전을 읽으시려면 클릭하세요
+ Video tutorial를 시청하세요.]]>
+ 퍼미션 세팅에 문제가 있을 수 있습니다.
+
+ Umbraco를 문제없이 실행할 수 있지만, 폴더를 만들거나 추천패키지를 설치하지 못할 수 있습니다.]]>
+ 권한 설정이 완료되지 않았습니다!
+
+ Umbraco 실행을 위해, 권한설정을 업데이트하세요.]]>
+ 권한세팅이 완벽합니다!
+ Umbraco 패키지 설치를 진행할 준비가 되었습니다. ]]>
+ 폴더 문제해결
+ 다음 링크는 ASP.NET이나 폴더생성문제에 대한 더 많은 정보를 제공합니다.
+ 폴더 권한 세팅
+ Umbraco 는 특정 디렉토리에 쓰기/수정 권한이 필요합니다. 이것은 PDF나 그림과 같은 파일을 저장하고 cache같은 임시데이터을 위해 사용됩니다.
+ scratch를 시작하기 원합니다.
+ learn how)
+ Runway설치를 나중에 실행하실 수 있습니다. 개발도구 부분에서 패키지를 선택하세요.
+ ]]>
+ 여러분은 Umbraco 플랫폼 설치를 완료하였습니다. 다음엔 어떤 작업을 원하십니까?
+ Runway 가 설치됨
+
+ 이것은 저희가 권장하는 모듈들입니다. 설치를 원하시는 모듈을 확인하세요 모듈이 없다면 전체 모듈리스트를 보세요
+ ]]>
+ 경험이 있는 사용자 분들만 추천합니다.
+ 간단한 웹사이트 생성을 원합니다.
+
+ "Runway" 는 간단한 웹사이트 생성을 위한 기본 문서타입과 템플릿을 제공합니다. 인스톨러를 이용해 Runway를 자동으로 설치하신 후
+ 여러분은 쉽게 수정, 확장, 삭제가 가능하십니다.
+ Umbraco에 익숙하시다면 Runway 가 필요없지만 그렇지 않으신경우 Runway는 가장 빨리 시작할 수 있는 최고의 예제를 제공합니다.
+ Runway 설치를 선택하시면, 여러분은 옵션으로 Runway 페이지에 쓰이는 Runway 모듈로 불리는 기본 빌딩 블록들을 선택하실 수 있습니다.
+
+
+ Runway 포함사항: 홈페이지, Getting Started 페이지, 모듈 설치페이지.
+ 옵션 모듈들: 네이게이션, 사이트맵, 연락처, 갤러리.
+
+ ]]>
+ Runway 란?
+ Step 1/5 라이센스 허가
+ Step 2/5 데이터베이스 설정
+ Step 3/5 파일권한 확인
+ Step 4/5 Umbraco 보안설정 확인
+ Step 5/5 Umbraco를 시작할 준비가 되었습니다.
+ Umbraco를 선택해주셔서 감사합니다.
+ 새 사이트 보기
+ Runway가 설치되었습니다, 새 웹사이트를 볼 수 있습니다.]]>
+ 고급 도움말과 정보
+ 우수 커뮤니티에서 도음을 받으세요. 간단한 사이트제작이나 패키지 사용법, Umbraco기술의 퀵가이드를 제공하는 문서를 보시거나 무료 비디오를 시청하세요.]]>
+ Umbraco %0% 가 설치되어 사용준비가 되었습니다.
+ /web.config file을 수동으로 편집해야 합니다. AppSetting 키의 UmbracoConfigurationStatus 를 '%0%'의 값으로 설정하세요.]]>
+ Umbraco 와 첫만남이시면 아래의 "Umbraco 접속하기" 버튼을 클릭하여 즉시 시작하실 수 있습니다.
+ 시작페이지에서 풍부한 리소소를 제공받을 수 있습니다.]]>
+ Umbraco 실행
+사이트 관리를 위해서 Umbraco 관리자를 여시고 컨텐츠를 추가하시거나 템플릿과 스타일시트 업데이트 또는 새기능을 추가하세요]]>
+ 데이터베이스에 연결 실패
+ Umbraco 버전 3
+ Umbraco 버전 4
+ 보기
+ Umbraco %0% 로 신규설치나 업그레이드가 가능하도록 도와줍니다.
+
]]>
+ %1%에 대한 [%0]알림이 %2%에 생성되었습니다
+ 알림
+
+
+
+ Umbraco 패키지는 보통 ".umb" 나 ".zip" 확장자를 가집니다.
+ ]]>
+ 저자
+ 데모
+ 문서화
+ 패키지 메타데이터
+ 패키지 이름
+ 패키지가 포함한 아이템이 없습니다.
+
+ 아래 "패키지 삭제"를 클릭하시면 안전하게 시스템에서 삭제하실 수 있습니다.]]>
+ 업그레이드할 패키지가 없습니다.
+ 패키지 옵션
+ 패키지 정보
+ 패키지 저장소
+ 삭제 확인
+ 패키지가 삭제되었습니다.
+ 패키지가 성공적으로 삭제되었습니다.
+ 패키지 삭제
+
+ 알림: 문서, 미디어등 삭제항목에 관련된 모든 항목이 삭제됩니다, 작업을 중단하면 시스템이 불안정적으로 동작할 수 있습니다.
+ 삭제는 매우 주의를 요하기 때문에 의심스러운항목은 패키지 제작자에게 문의하시기 바랍니다.]]>
+ 저장소에서 업데이트 다운로드
+ 업그레이드 패키지
+ 업그레이드 지시사항
+ 업그레이드할 패키지가 없습니다. Umbraco패키지 저장소에서 직접다운로드하실 수 있습니다.
+ 패키지 버전
+ 패키지 웹사이트 보기
+
+
+ 포맷을 포함여하 붙여넣기(권장하지 않음)
+ 붙여넣으려는 텍스트에 특수한 문자나 포맷이 포함되어있습니다. Microsoft Word문서에서 바로 복사해와서 문제가 발생된것일 수 있습니다. Umbraco는 붙여넣으려는 컨텐츠가 웹에 적합하도록 특수한 문자나 포맷을 자동으로 제거합니다
+ 포맷을 전혀 적용하지 않고 붙여넣기
+ 포맷을 제거하고 붙여넣기(권장)
+
+
+ 역할 기반 제한
+ Umbraco의 사용자그룹을 사용하세요.]]>
+ 사용자 그룹부터 생성해야합니다.]]>
+ 에러 페이지
+ 로그인을 시도할 때 접근할 수 없습니다.
+ 이페이지의 접근제한을 어떻게 제한할지 선택하세요
+ %0% 제한되었습니다.
+ %0% 의 제한이 제거되었습니다
+ 로그인 페이지
+ 로그인 폼 양식페이지를 선택하세요
+ 제한 제거
+ 로그인 폼과 에러메세지가 포함된 페이지를 선택하세요
+ 이 페이지에 접근할 역할을 선택하세요
+ 이 페이지에 로그인과 암호 설정
+ 사용자 제한
+ 로그인과 암호를 이용해 사용자 제한
+
+
+ 제3공급자 익스텐션 취소때문에 %0% 가 발행할 수없습니다.
+ 미발행된 자식 문서 포함
+ 발행 진행중 - 잠시만 기다리세요...
+ %1% 페이지를 제외한 %0% 가 발행됨...
+ %0% 발행됨
+ %0% 과 서브페이지가 발행되었습니다
+ %0% 와 모든 서브페이지 발행
+ %0%를 발행하기위해 확인를 클릭하세요 and thereby making it's content publicly available.
+ 이 페이지와 모든 서브페이지를 아래 모든 자식문서 발행을 체크하여 발행할 수 있습니다.
+ ]]>
+
+
+ 외부링크 추가
+ 내부링크 추가
+ 추가
+ 설명
+ 내부 페이지
+ URL
+ 아래로 이동
+ 위로 이동
+ 새 창 열기
+ 링크 삭제
+
+
+ 현재 버전
+ 빨간 텍스트는 선택한 버전에선 보이지 않습니다. 녹색은 추가되었음을 의미합니다]]>
+ 문서가 롤백되었습니다.
+ 선택한 버전을 html로 보여줍니다. 두 버전의 차이점을 동시에 보시려면, 차이점 보기를 사용하세요
+ 롤백
+ 버전 선택
+ 보기
+
+
+ 스크립트 파일 편집
+
+
+ 안내
+ 컨텐츠
+ 가이드
+ 개발도구
+ Umbraco 설치마법사
+ 미디어
+ 구성원
+ 뉴스레터
+ 세팅
+ 통계
+ 변환
+ 사용자
+
+
+ 기본 템플릿
+ 사전 키
+ 문서를 가져오시려면 사용하시는 컴퓨터에 ".udt"를 찾아 선택하시고 "가져오기"를 클릭하세요(다음 단계에서 확인여부를 문의합니다)
+ 새 색인 제목
+ 노드타입
+ 타입
+ 스타일시트
+ 스타일시트 속성
+ 색인
+ 색인 제목
+ 색인
+
+
+ Sort order
+ Creation date
+ 정렬 완료
+ 다른 아이템을 마우스로 위,아래로 드래그 하여 이동하거나 열의 헤더를 클릭하여 아이템을 정렬할 수 있습니다
+ 정렬하는 동안 이 창을 닫지 마십시오]]>
+
+
+ 3rd party add-in 때문에 발행이 취소되었습니다.
+ 속성타입이 이미존재합니다
+ 속성타입 생성되었습니다
+ 데이터타입: %1%]]>
+ 속성타입 삭제됨
+ 컨텐츠타입 저장됨
+ 색인 생성
+ 색인 삭제
+ Tab with id: %0% 삭제됨
+ 스타일시트 저장되지 않음
+ 스타일시트 저장
+ 스타일시트 에러없이 저장
+ 데이터타입 저장됨
+ 사전 항목 저장됨
+ 부모페이지가 발행되지 않았기때문에 발행에 실패했습니다.
+ 컨텐츠 발행됨
+ and 웹사이트에서 보기
+ 컨텐츠 저장됨
+ 변경된 내용이 적용되어 발행됨을 기억하세요
+ 승인을 위해 전송
+ 변경사항이 승인을 위해 전송되었습니다.
+ 사용자 저장됨
+ 스타일시트 속성 저장됨
+ 스타일시트 저장됨
+ 템플릿 저장됨
+ 사용자 저장에러(로그 확인)
+ 사용자 저장됨
+ 파일 저장되지 않음
+ 파일이 저장되지 않았습니다. 권한을 확인하세요
+ 파일 저장
+ 파일이 에러없이 저장
+ 언어 저장됨
+ Python 스크립트가 저장되지 않았습니다.
+ Python 스크립트가 에러때문에 저장되지 않았습니다.
+ Python 스크립트 저장
+ Python 스크립트 에러없음
+ 템플릿이 저장되지 않음
+ 2 템플릿에 동일한 별칭이 적용되지 않았는지 확인하시기 바랍니다.
+ 템플릿 저장
+ 탬플릿이 에러없이 저장되었습니다!
+ XSLT 저장되지 않음
+ XSLT 에 에러가 포함됨
+ XSLT가 저장되지 않았습니다. 권한을 확인하세요
+ XSLT 저장
+ XSLT 에러없음
+
+
+ CSS 태그를 사용하세요 예: h1, .redHeader, .blueTex
+ 스타일시트 편집
+ 스타일시트 속성편집
+ rich text editor 에 스타일속성을 확인할 수 있는 이름을 붙이세요
+ 미리보기
+ 스타일
+
+
+ 템플릿 편집
+ 컨텐츠범위 삽입
+ 컨텐츠범위 Placeholder 삽입
+ 사전 항목 삽입
+ 매크로 삽입
+ Umbraco 페이지필드 삽입
+ 마스터 템플릿
+ Umbraco 템플릿태그 퀵가이드
+ 템플릿
+
+
+ Choose type of content
+ Choose a layout
+ Add a row
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Click to embed
+ Click to insert image
+ Image caption...
+ Write here...
+
+ Grid Layouts
+ Layouts are the overall work area for the grid editor, usually you only need one or two different layouts
+ Add Grid Layout
+ Adjust the layout by setting column widths and adding additional sections
+ Row configurations
+ Rows are predefined cells arranged horizontally
+ Add row configuration
+ Adjust the row by setting cell widths and adding additional cells
+
+ Columns
+ Total combined number of columns in the grid layout
+
+ Settings
+ Configure what settings editors can change
+
+ Styles
+ Configure what styling editors can change
+
+ Settings will only save if the entered json configuration is valid
+
+ Allow all editors
+ Allow all row configurations
+
+
+ 대체 필드
+ 대체 글꼴
+ Casing
+ 필드 선택
+ 줄바꿈문자 변환
+ 줄바꿈문자를 Html태그 &lt;br&gt; 로 변경
+ 예, 날짜만
+ 날짜포맷으로
+ HTML 인코딩
+ HTML과 동일하게 특수문자를 변경하시겠습니까
+ 필드 값 후에 삽입하시겠습니까
+ 필드값 전에 삽입하시겠습니까
+ 소문자
+ 없음
+ 필드뒤에 삽입
+ 필드앞에 삽입
+ Recursive
+ 단락 태그삭제
+ 문서 시작과 끝의 &lt;P&gt; 를 삭제하시겠습니까
+ 대문자
+ URL 인코딩
+ URL의 특수문자를 포맷하겠습니까
+ 필드 위의 값들이 비었을때만 사용가능합니다.
+ 이 필드는 최초필드가 비었을때만 사용가능합니다.
+ 예, 시간를 :로 구분하여
+
+
+ 할당된 작업
+ 당신에게 할당된 번역작업들을 볼 수 있습니다.
+ 상세내역을 보시려면 "상세" 나 페이지 이름을 클릭하세요.
+ "Xml 다운로드" 링크를 클릭하시면 Xml로 페이지를 다운로드 할 수 있습니다.
+ 번역작업을 닫으시려면, 상세보기로 가셔서 "닫기" 버튼을 클릭하세요.
+ ]]>
+ 작업 닫기
+ 번역 세부항목
+ 모든 번역작업을 XML로 다운로드
+ XML 다운로드
+ 다운로드 xml DTD
+ 필드
+ 서브페이지 포함
+
+ Translation task for %1%을 위한 [%0%] 번역작업
+ 번역자를 찾을 수 없습니다. 컨텐츠를 번역하기위해 발송하시기 전에 번역자를 생성하세요.
+ 생성한 작업
+ 생성한 작업 페이지를 볼 수 있습니다.
+ 상세내역을 보시려면 "상세" 나 페이지 이름을 클릭하세요.
+ "Xml 다운로드" 링크를 클릭하시면 Xml로 페이지를 다운로드 할 수 있습니다.
+ 번역작업을 닫으시려면, 상세보기로 가셔서 "닫기" 버튼을 클릭하세요.
+ ]]>
+ '%0%' 페이지가 번역을 위해 전송되었습니다.
+ 번역을 위해 '%0%' 페이지 전송하기Send the page '%0%' to translation
+ 할당자
+ 작업 열기
+ 총 단어 수
+ 번역
+ 번역 완료
+ 아래를 클릭하셔서 방금 번역한 페이지를 미리볼 수 있습니다. 원본 페이지가 있다면 두 페이지를 비교해보시기 바랍니다.
+ 번역에 실패했습니다. Xml 파일에 문제가 있을수 있습니다.
+ 번역 옵션
+ 번역자
+ 번역 XML 업로드
+
+
+ 캐시 브라우저
+ 휴지통
+ 생성된 패키지
+ 데이터 타입
+ 사전
+ 설치된 패키지
+ TRANSLATE ME: 'Install skin'
+ TRANSLATE ME: 'Install starter kit'
+ 언어
+ 로컬 패키지 설치
+ 매크로
+ 미디어 타입
+ 구성원
+ 구성원 그룹
+ 역할
+ 구성원 유형
+ 문서 타입
+ 패키지
+ 패키지
+ Python 파일
+ 저장소에 설치
+ Runway 설치
+ Runway 모듈
+ 스크립트 파일
+ 스크립트
+ 스타일시트
+ 템플릿
+ XSLT 파일
+
+
+ 새 업데이트가 준비되었습니다.
+ %0% 가 준비되었습니다. 다운로드를 위해 여기를 클릭하세요
+ 연결할 서버가 없습니다연결할 서버가 없습니다
+ 업데이트을 위해 에러를 체크합니다 더많은 정보를 보시려면 stack 추적을 하세요
+
+
+ 관리자
+ 카테고리 필드
+ Change Your Password
+ You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button
+ 컨텐츠 채널
+ 설명 필드
+ 사용자 비활성화
+ 문서 타입
+ 편집자
+ 필드 발췌
+ 언어
+ 로그인
+ 미디어 라이브러리에 시작노드
+ 세부항목
+ Umbraco 접속 비활성화
+ 비밀번호
+ Your password has been changed!
+ Please confirm the new password
+ Enter your new password
+ Your new password cannot be blank!
+ There was a difference between the new password and the confirmed password. Please try again!
+ The confirmed password doesn't match the new password!
+ 자식노드 권한변경
+ 현재 이페이지의 권한을 수정하는 중입니다:
+ 권한변경할 페이지를 선택해주세요
+ 하위항목 모두찾기
+ 컨텐츠의 시작노드
+ 사용자명
+ 사용자권한
+ 사용자 타입
+ 사용자 타입
+ 작성자
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Config/Lang/nb.xml b/WebCms/Umbraco/Config/Lang/nb.xml
new file mode 100644
index 0000000..511e9ad
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/nb.xml
@@ -0,0 +1,1017 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Angi domene
+ Revisjoner
+ Bla gjennom
+ Skift dokumenttype
+ Kopier
+ Opprett
+ Opprett pakke
+ Slett
+ Deaktiver
+ Tøm papirkurv
+ Eksporter dokumenttype
+ Importer dokumenttype
+ Importer pakke
+ Rediger i Canvas
+ Logg av
+ Flytt
+ Varslinger
+ Offentlig tilgang
+ Publiser
+ Avpubliser
+ Oppdater noder
+ Republiser hele siten
+ Gjenopprett
+ Rettigheter
+ Reverser
+ Send til publisering
+ Send til oversetting
+ Sorter
+ Send til publisering
+ Oversett
+ Oppdater
+ Standard verdi
+
+
+ Ingen tilgang.
+ Legg til domene
+ Fjern
+ Ugyldig node.
+ Ugyldig domeneformat.
+ Domene er allerede tilknyttet.
+ Språk
+ Domene
+ Domene '%0%' er nå opprettet og tilknyttet siden
+ Domenet '%0%' er nå slettet
+ Domenet '%0%' er allerede tilknyttet
+ Domenet '%0%' er nå oppdatert
+ eller rediger eksisterende domener
+ Stier med ett nivå støttes, f.eks. "eksempel.com/no". Imidlertid bør det unngås. Bruk heller språkinnstillingen over.]]>
+ Arv
+ Språk
+ Vil også gjelde denne noden, med mindre et underordnet domene også gjelder.]]>
+ Domener
+
+
+ Viser for
+
+
+ Velg
+ Velg gjeldende mappe
+ Gjør noe annet
+ Fet
+ Reduser innrykk
+ Sett inn skjemafelt
+ Sett inn grafisk overskrift
+ Rediger HTML
+ Øk innrykk
+ Kursiv
+ Midtstill
+ Juster tekst venstre
+ Juster tekst høyre
+ Sett inn lenke
+ Sett inn lokal lenke (anker)
+ Punktmerking
+ Nummerering
+ Sett inn makro
+ Sett inn bilde
+ Rediger relasjoner
+ Tilbake til listen
+ Lagre
+ Lagre og publiser
+ Lagre og send til publisering
+ Forhåndsvis
+ Forhåndsvisning er deaktivert siden det ikke er angitt noen mal
+ Velg formattering
+ Vis stiler
+ Sett inn tabell
+
+
+ For å endre det valge innholdets dokumenttype, velger du først en ny dokumenttype som er gyldig på gjeldende plassering.
+ Kontroller deretter at alle egenskaper blir overført riktig til den nye dokumenttypen og klikk på Lagre.
+ Innholdet har blitt republisert.
+ Nåværende egenskap
+ Nåværende type
+ Du kan ikke endre dokumenttype, ettersom det ikke er andre gyldige dokumenttyper på denne plasseringen.
+ Dokumenttype endret
+ Overfør egenskaper
+ Overfør til egenskap
+ Ny mal
+ Ny type
+ ingen
+ Innhold
+ Velg ny dokumenttype
+ Dokumenttypen på det valgte innhold ble endret til [new type], og følgende egenskaper ble overført:
+ til
+ Overføringen av egenskaper kunne ikke fullføres da en eller flere egenskaper er satt til å bli overført mer enn en gang.
+ Kun andre dokumenttyper som er gyldige for denne plasseringen vises.
+
+
+ Publisert
+ Om siden
+ Alias
+ (hvordan du ville beskrevet bildet over telefon)
+ Alternative lenker
+ Klikk for å redigere denne noden
+ Opprettet av
+ Opprinnelig forfatter
+ Oppdatert av
+ Opprettet den
+ Tidspunkt for opprettelse
+ Dokumenttype
+ Redigerer
+ Utløpsdato
+ Denne noden er endret siden siste publisering
+ Denne noden er enda ikke publisert
+ Sist publisert
+ Det er ingen elementer å vise i listen.
+ Mediatype
+ Link til media
+ Medlemsgruppe
+ Rolle
+ Medlemstype
+ Ingen dato valgt
+ Sidetittel
+ Egenskaper
+ Dette dokumentet er publisert, men ikke synlig ettersom den overliggende siden '%0%' ikke er publisert
+ Intern feil: dokumentet er publisert men finnes ikke i hurtigbuffer
+ Publisert
+ Publiseringsstatus
+ Publiseringsdato
+ Dato for avpublisering
+ Fjern dato
+ Sorteringsrekkefølgen er oppdatert
+ Trekk og slipp nodene eller klikk på kolonneoverskriftene for å sortere. Du kan velge flere noder ved å holde shift eller control tastene mens du velger.
+ Statistikk
+ Tittel (valgfri)
+ Alternativ tekst (valgfri)
+ Type
+ Avpubliser
+ Sist endret
+ Tidspunkt for siste endring
+ Fjern fil
+ Lenke til dokument
+ Medlem av gruppe(ne)
+ Ikke medlem av gruppe(ne)
+ Undersider
+ Åpne i vindu
+
+
+ Klikk for å laste opp
+ Slipp filene her...
+
+
+ Opprett et nytt medlem
+ Alle medlemmer
+
+
+ Hvor ønsker du å oprette den nye %0%
+ Opprett under
+ Velg en type og skriv en tittel
+ "dokumenttyper".]]>
+ "mediatyper".]]>
+
+
+ Til ditt nettsted
+ - Skjul
+ Hvis Umbraco ikke starter, kan det skyldes at pop-up vinduer ikke er tillatt
+ er åpnet i nytt vindu
+ Omstart
+ Besøk
+ Velkommen
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Navn på lokal link
+ Rediger domener
+ Lukk dette vinduet
+ Er du sikker på at du vil slette
+ Er du sikker på at du vil deaktivere
+ Vennligst kryss av i denne boksen for å bekrefte sletting av %0% element(er)
+ Er du sikker på at du vil forlate Umbraco?
+ Er du sikker?
+ Klipp ut
+ Rediger ordboksnøkkel
+ Rediger språk
+ Sett inn lokal link
+ Sett inn spesialtegn
+ Sett inn grafisk overskrift
+ Sett inn bilde
+ Sett inn lenke
+ Sett inn makro
+ Sett inn tabell
+ Sist redigert
+ Lenke
+ Intern link:
+ Ved lokal link, sett inn "#" foran link
+ Åpne i nytt vindu?
+ Makroinnstillinger
+ Denne makroen har ingen egenskaper du kan endre
+ Lim inn
+ Endre rettigheter for
+ Innholdet i papirkurven blir nå slettet. Vennligst ikke lukk dette vinduet mens denne operasjonen foregår
+ Papirkurven er nå tom
+ Når elementer blir slettet fra papirkurven vil de være slettet for alltid
+ regexlib.com tjenesten opplever for tiden problemer som vi ikke har kontroll over. Vi beklager denne ubeleiligheten.]]>
+ Søk etter et regulært uttrykk for å legge inn validering til et felt. Eksempel: 'email, 'zip-code' 'url'
+ Fjern makro
+ Obligatorisk
+ Nettstedet er indeksert
+ Hurtigbufferen er blitt oppdatert. Alt publisert innhold er nå à jour. Alt upublisert innhold er fortsatt ikke publisert.
+ Hurtigbufferen for siden vil bli oppdatert. Alt publisert innhold vil bli oppdatert, mens upublisert innhold vil forbli upublisert.
+ Antall kolonner
+ Antall rader
+ Sett en plassholder-ID Ved å sette en ID på plassholderen kan du legge inn innhold i denne malen fra underliggende maler, ved å referere denne ID'en ved hjelp av et <asp:content /> element.]]>
+ Velg en plassholder ID fra listen under. Du kan bare velge ID'er fra den gjeldende malens overordnede mal.]]>
+ Klikk på bildet for å se det i full størrelse
+ Velg punkt
+ Se buffret node
+
+
+ %0%' under. Du kan legge til flere språk under 'språk' i menyen til venstre.]]>
+ Språk
+
+
+ Skriv inn ditt brukernavn
+ Skriv inn ditt passord
+ Navngi %0%...
+ Skriv inn navn...
+ Søk...
+ Filtrer...
+ Skriv inn nøkkelord (trykk på Enter etter hvert nøkkelord)...
+
+
+ Tillat på rotnivå
+ Kun dokumenttyper med denne innstillingen aktivert kan opprettes på rotnivå under Innhold og Mediearkiv
+ Tillatte underordnede noder
+ Sammensetting av dokumenttyper
+ Opprett
+ Slett arkfane
+ Beskrivelse
+ Ny arkfane
+ Arkfane
+ Miniatyrbilde
+ Aktiver listevisning
+ Viser undersider i en søkbar liste, undersider vises ikke i innholdstreet
+ Gjeldende listevisning
+ Den aktive listevisningsdatatypen
+ Opprett brukerdefinert listevisning
+ Fjern brukerdefinert listevisning
+
+
+ Legg til forhåndsverdi
+ Database datatype
+ Kontrollelement GUID
+ Kontrollelement
+ Knapper
+ Aktiver avanserte instillinger for
+ Aktiver kontektsmeny
+ Maksimum standard størrelse på innsatte bilder
+ Beslektede stilark
+ Vis etikett
+ Bredde og høyde
+
+
+ Dine data har blitt lagret, men før du kan publisere denne siden må du rette noen feil:
+ Den gjeldende Membership Provider støtter ikke endring av passord. (EnablePasswordRetrieval må være satt til sann)
+ %0% finnes allerede
+ Det var feil i dokumentet:
+ Det var feil i skjemaet:
+ Passordet bør være minst %0% tegn og inneholde minst %1% numeriske tegn
+ %0% må være et heltall
+ %0% under %1% er obligatorisk
+ %0% er obligatorisk
+ %0% under %1% er ikke i et korrekt format
+ %0% er ikke i et korrekt format
+
+
+ Filtypen er deaktivert av administrator
+ NB! Selv om CodeMirror er aktivert i konfigurasjon er det deaktivert i Internet Explorer pga. ustabilitet.
+ Fyll ut både alias og navn på den nye egenskapstypen!
+ Det er et problem med lese/skrive rettighetene til en fil eller mappe
+ Tittel mangler
+ Type mangler
+ Du er i ferd med å gjøre bildet større enn originalen. Det vil forringe kvaliteten på bildet, ønsker du å fortsette?
+ Feil i python-skriptet
+ Python-skriptet ble ikke lagret fordi det inneholder en eller flere feil
+ Startnode er slettet. Kontakt din administrator
+ Du må markere innhold før du kan endre stil
+ Det er ingen aktive stiler eller formateringer på denne siden
+ Sett markøren til venstre i de 2 cellene du ønsker å slå sammen
+ Du kan ikke dele en celle som allerede er delt.
+ Feil i XSLT kode
+ XSLT ble ikke lagret på grunn av feil i koden
+ Det er et problem dem datatypen som brukes til denne egenskapen. Kontroller innstillingene og prøv igjen.
+
+
+ Om
+ Handling
+ Muligheter
+ Legg til
+ Alias
+ Er du sikker?
+ Ramme
+ av
+ Avbryt
+ Cellemargin
+ Velg
+ Lukk
+ Lukk vindu
+ Kommentar
+ Bekreft
+ Behold proposjoner
+ Fortsett
+ Kopier
+ Opprett
+ Database
+ Dato
+ Standard
+ Slett
+ Slettet
+ Sletter...
+ Design
+ Dimensjoner
+ Ned
+ Last ned
+ Rediger
+ Endret
+ Elementer
+ E-post
+ Feil
+ Finn
+ Høyde
+ Hjelp
+ Ikon
+ Importer
+ Indre margin
+ Sett inn
+ Installer
+ Justering
+ Språk
+ Layout
+ Laster
+ Låst
+ Logg inn
+ Logg ut
+ Logg ut
+ Makro
+ Flytt
+ Mer
+ Navn
+ Ny
+ Neste
+ Nei
+ av
+ OK
+ Åpne
+ eller
+ Passord
+ Sti
+ Plassholder ID
+ Ett øyeblikk...
+ Forrige
+ Egenskaper
+ E-post som innholdet i skjemaet skal sendes til
+ Papirkurv
+ Gjenværende
+ Gi nytt navn
+ Forny
+ Påkrevd
+ Prøv igjen
+ Rettigheter
+ Søk
+ Server
+ Vis
+ Hvilken side skal vises etter at skjemaet er sendt
+ Størrelse
+ Sorter
+ Submit
+ Type
+ Søk...
+ Opp
+ Oppdater
+ Oppgrader
+ Last opp
+ Url
+ Bruker
+ Brukernavn
+ Verdi
+ Visning
+ Velkommen...
+ Bredde
+ Ja
+ Mappe
+ Søkeresultater
+
+
+ Bakgrunnsfarge
+ Fet
+ Tekstfarge
+ Skrifttype
+ Tekst
+
+
+ Side
+
+
+ Installasjonsprogrammet kan ikke koble til databasen
+ Kunne ikke lagre Web.Config-filen. Vennligst endre databasens tilkoblingsstreng manuelt.
+ Din database er funnet og identifisert som
+ Databasekonfigurasjon
+ installer-knappen for å installere Umbraco %0% databasen]]>
+ Neste for å fortsette.]]>
+ Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.
For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.
]]>
+ Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator.]]>
+ Trykk på knappen oppgrader for å oppgradere databasen din til Umbraco %0%
Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå!
]]>
+ Trykk Neste for å fortsette.]]>
+ neste for å fortsette konfigurasjonsveiviseren]]>
+ Passordet til standardbrukeren må endres!]]>
+ Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!
Ingen videre handling er nødvendig. Klikk neste for å fortsette.]]>
+ Passordet til standardbrukeren har blitt forandret etter installasjonen!
Ingen videre handling er nødvendig. Klikk Neste for å fortsette.]]>
+ Passordet er blitt endret!
+ Umbraco skaper en standard bruker med login ( "admin") strong> og passord ( "default") strong>. Det er viktig strong> at passordet er endret til noe unikt. p>
Dette trinnet vil sjekke standard brukerens passord og foreslår hvis det må skiftes p>]]>
+ Få en god start med våre introduksjonsvideoer
+ Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet.
+ Ikke installert.
+ Berørte filer og mapper
+ Mer informasjon om å sette opp rettigheter for Umbraco her
+ Du må gi ASP.NET brukeren rettigheter til å endre de følgende filer og mapper
+ Rettighetene er nesten perfekt satt opp!
Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]>
+ Hvordan løse problemet
+ Klikk her for å lese tekstversjonen
+ innføringsvideo om å sette opp rettigheter for Umbraco eller les tekstversjonen.]]>
+ Rettighetsinnstillingene kan være et problem!
Du kan kjøre Umbraco uten problemer, men du vil ikke være i stand til å installere de anbefalte pakkene for å utnytte Umbraco fullt ut.]]>
+ Rettighetsinstillingene er ikke klargjort for Umbraco!
For å kunne kjøre Umbraco, må du oppdatere rettighetsinnstillingene dine.]]>
+ Rettighetsinnstillingene er perfekt!
Du er klar for å kjøre Umbraco og installere pakker!]]>
+ Løser mappeproblem
+ Følg denne linken for mer informasjon om problemer med ASP.NET og oppretting av mapper
+ Konfigurerer mappetillatelser
+
+ Jeg ønsker å starte fra bunnen.
+ lær hvordan) Du kan fortsatt velge å installere Runway senere. Vennligst gå til Utvikler-seksjonen og velg Pakker.]]>
+ Du har akkurat satt opp en ren Umbraco plattform. Hva vil du gjøre nå?
+ Runway er installert
+ Dette er vår liste av anbefalte moduler- Kryss av de du ønsker å installere, eller se denfulle listen av moduler ]]>
+ Bare anbefalt for erfarne brukere
+ Jeg vil starte med en enkel webside
+ "Runway" er en enkel webside som utstyrer deg med noen grunnleggende dokumenttyper og maler. Veiviseren kan sette opp Runway for deg automatisk, men du kan enkelt endre, utvide eller slette den. Runway er ikke nødvendig, og du kan enkelt bruke Umbraco uten den. Imidlertidig tilbyr Runway et enkelt fundament basert på de beste metodene for å hjelpe deg i gang fortere enn noensinne. Hvis du velger å installere Runway, kan du også velge blant grunnleggende byggeklosser kalt Runway Moduler for å forøke dine Runway-sider. Sider inkludert i Runway: Hjemmeside, Komme-i-gang, Installere moduler. Valgfrie Moduler: Toppnavigasjon, Sidekart, Kontakt, Galleri. ]]>
+ Hva er Runway
+ Steg 1/5 Godta lisens
+ Steg 2/5 Database konfigurasjon
+ Steg 3/5: Valider filrettigheter
+ Steg 4/5: Skjekk Umbraco sikkerheten
+ Steg 5/5: Umbraco er klar for deg til å starte!
+ Tusen takk for at du valgte Umbraco!
+ Se ditt nye nettsted Du har installert Runway, hvorfor ikke se hvordan ditt nettsted ser ut.]]>
+ Mer hjelp og info Få hjelp fra vårt prisbelønte samfunn, bla gjennom dokumentasjonen eller se noen gratis videoer på hvordan man bygger et enkelt nettsted, hvordan bruke pakker og en rask guide til Umbraco terminologi]]>
+ Umbraco %0% er installert og klar til bruk
+ web.config filen, og oppdatere AppSetting-nøkkelen UmbracoConfigurationStatus til verdien '%0%']]>
+ starte øyeblikkelig ved å klikke på "Start Umbraco" knappen nedenfor. Hvis du er ny på Umbraco, kan du finne mange ressurser på våre komme-i-gang sider.]]>
+ Start Umbraco For å administrere din webside, åpne Umbraco og begynn å legge til innhold, oppdatere maler og stilark eller utvide funksjonaliteten]]>
+ Tilkobling til databasen mislyktes.
+ Umbraco Versjon 3
+ Umbraco Versjon 4
+ Se
+ Umbraco %0% for en ny installasjon eller oppgradering fra versjon 3.0.
]]>
+ [%0%] Varsling om %1% utført på %2%
+ Varslinger
+
+
+ Umbraco-pakker har vanligvis endelsen ".umb" eller ".zip".]]>
+ Utvikler
+ Demonstrasjon
+ Dokumentasjon
+ Metadata
+ Pakkenavn
+ Pakken inneholder ingen elementer
+ Du kan trygt fjerne pakken fra systemet ved å klikke "avinstaller pakke" nedenfor.]]>
+ Ingen oppdateringer tilgjengelig
+ Alternativer for pakke
+ Lesmeg for pakke
+ Pakkebrønn
+ Bekreft avinstallering
+ Pakken ble avinstallert
+ Pakken ble vellykket avinstallert
+ Avinstaller pakke
+ Advarsel: alle dokumenter, media, etc. som som er avhengig av elementene du sletter, vil slutte å virke, noe som kan føre til ustabilitet, så avinstaller med forsiktighet. Hvis du er i tvil, kontakt pakkeutvikleren.]]>
+ Last ned oppdatering fra pakkeregisteret
+ Oppgrader pakke
+ Oppgraderingsinstrukser
+ Det er en oppdatering tilgjengelig for denne pakken. Du kan laste den ned direkte fra pakkebrønnen.
+ Pakkeversjon
+ Pakkeversjonshistorie
+ Se pakkens nettsted
+
+
+ Lim inn med full formattering (Anbefales ikke)
+ Teksten du er i ferd med å lime inn, inneholder spesialtegn eller formattering. Dette kan skyldes at du kopierer fra f.eks. Microsoft Word. Umbraco kan fjerne denne spesialformatteringen automatisk slik at innholdet er mer velegnet for visning på en webside.
+ Lim inn som ren tekst, dvs. fjern al formattering
+ Lim inn og fjern uegnet formatering (anbefalt)
+
+
+ Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden
+ ved å bruke Umbraco's medlems-grupper]]>
+ rollebasert autentikasjon.]]>
+ Feilside
+ Brukt når personer logger på, men ikke har tilgang
+ Hvordan vil du beskytte siden din?
+ %0% er nå beskyttet
+ Beskyttelse fjernet fra %0%
+ Innloggingsside
+ Velg siden som har loginformularet
+ Fjern beskyttelse
+ Velg sidene som inneholder login-skjema og feilmelding ved feil innolgging.
+ Velg rollene som har tilgang til denne siden
+ Sett brukernavn og passord for denne siden
+ Enkelt: Beskytt ved hjelp av brukernavn og passord
+ Om du ønsker å bruke enkel autentisering via ett enkelt brukernavn og passord
+
+
+ %0% kunne ikke publiseres fordi den har planlagt utgivelsesdato.
+ %0% ble ikke publisert. Ett eller flere felter ble ikke godkjent av validering.
+ %0% kunne ikke publiseres fordi et tredjepartstillegg avbrøt handlingen.
+ %0% kan ikke publiseres fordi en overordnet side ikke er publisert.
+ Inkluder upubliserte undersider
+ Publiserer - vennligst vent...
+ %0% av %1% sider har blitt publisert...
+ %0% er nå publisert
+ %0% og alle undersider er nå publisert
+ Publiser alle undersider
+ ok for å publisere %0% og dermed gjøre innholdet synlig for alle.
Du kan publisere denne siden og alle dens undersider ved å krysse av Publiser alle undersider nedenfor.]]>
+
+
+ Du har ikke konfigurert noen godkjente farger
+
+
+ skriv inn ekstern lenke
+ velg en intern side
+ Tittel
+ Lenke
+ Åpne i nytt vindu
+ Skriv inn en tekst
+ Skriv inn en lenke
+
+
+ Nullstill
+
+
+ Gjeldende versjon
+ Rød tekst vil ikke bli vist i den valgte versjonen. , grønn betyr lagt til]]>
+ Dokumentet er tilbakeført til en tidligere versjon
+ Dette viser den valgte versjonen som HTML, bruk avviksvisningen hvis du ønsker å se forksjellene mellom to versjoner samtidig.
+ Tilbakefør til
+ Velg versjon
+ Vis
+
+
+ Rediger scriptfilen
+
+
+ Concierge
+ Innhold
+ Courier
+ Utvikler
+ Umbraco konfigurasjonsveiviser
+ Mediaarkiv
+ Medlemmer
+ Nyhetsbrev
+ Innstillinger
+ Statistikk
+ Oversettelse
+ Brukere
+ Hjelp
+ Skjemaer
+ Analytics
+
+
+ gå til
+ Hjelpeemner for
+ Videokapitler for
+ De beste Umbraco opplæringsvideoer
+
+
+ Standardmal
+ Ordboksnøkkel
+ For å importere en dokumenttype, finn ".udt" filen på datamaskinen din ved å klikke "Utforsk" knappen og klikk "Importer" (du vil bli spurt om bekreftelse i det neste skjermbildet)
+ Ny tittel på arkfane
+ Nodetype
+ Type
+ Stilark
+ Script
+ Stilark-egenskap
+ Arkfane
+ Tittel på arkfane
+ Arkfaner
+ Hovedinnholdstype aktivert
+ Denne dokumenttypen bruker
+ som hoveddokumenttype. Arkfaner fra hoveddokumenttyper vises ikke og kan kun endres på hoveddokumenttypen selv.
+ Ingen egenskaper definert i denne arkfanen. Klikk på "legg til ny egenskap" lenken i toppen for å opprette en ny egenskap.
+ Hovedinnholdstype
+ Opprett tilhørende mal
+
+
+ Sort order
+ Creation date
+ Sortering ferdig.
+ Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang.
+ Ikke lukk dette vinduet under sortering]]>
+
+
+ En feil oppsto
+ Utilstrekkelige brukertillatelser, kunne ikke fullføre operasjonen
+ Avbrutt
+ Handlingen ble avbrutt av et tredjepartstillegg
+ Publisering ble avbrutt av et tredjepartstillegg
+ Egenskaptypen finnes allerede
+ Egenskapstype opprettet
+ DataType: %1%]]>
+ Egenskapstype slettet
+ Innholdstype lagret
+ Du har opprettet en arkfane
+ Arkfane slettet
+ Arkfane med id: %0% slettet
+ Stilarket ble ikke lagret
+ Stilarket ble lagret
+ Stilark lagret uten feil
+ Datatype lagret
+ Ordbokelement lagret
+ Publiseringen feilet fordi den overliggende siden ikke er publisert
+ Innhold publisert
+ og er nå synlig for besøkende
+ Innhold lagret
+ Husk å publisere for å gjøre endringene synlig for besøkende
+ Sendt for godkjenning
+ Endringer har blitt sendt til godkjenning
+ Media lagret
+ Media lagret uten feil
+ Medlem lagret
+ Stilarksegenskap lagret
+ Stilark lagret
+ Mal lagret
+ Feil ved lagring av bruker (sjekk loggen)
+ Bruker lagret
+ Brukertypen lagret
+ Filen ble ikke lagret
+ Filen kunne ikke lagres. Vennligst sjekk filrettigheter
+ Filen ble lagret
+ Filen ble lagret uten feil
+ Språk lagret
+ Python-skriptet ble ikke lagret
+ Python-skriptet kunne ikke lagres fordi det inneholder en eller flere feil
+ Python-skriptet er lagret!
+ Ingen feil i python-skriptet!
+ Malen ble ikke lagret
+ Vennligst forviss deg om at du ikke har to maler med samme alias
+ Malen ble lagret
+ Malen ble lagret uten feil!
+ XSLT-koden ble ikke lagret
+ XSLT-koden inneholdt en feil
+ XSLT-koden ble ikke lagret, sjekk filrettigheter
+ XSLT lagret
+ Ingen feil i XSLT!
+ Innhold avpublisert
+ Delmal lagret
+ Delmal lagret uten feil
+ Delmal ble ikke lagret!
+ En feil oppsto ved lagring av delmal
+ Script visning lagret
+ Script visning lagret uten feil!
+ Script visning ikke lagret
+ En feil oppsto under lagring av filen.
+ En feil oppsto under lagring av filen.
+
+
+ Bruk CSS syntaks f.eks: h1, .redHeader, .blueText
+ Rediger stilark
+ Rediger egenskap for stilark
+ Navn for å identifisere stilarksegenskapen i rik-tekst editoren
+ Forhåndsvis
+ Stiler
+
+
+ Rediger mal
+ Sett inn innholdsområde
+ Sett inn plassholder for innholdsområde
+ Sett inn ordbokselement
+ Sett inn makro
+ Sett inn Umbraco sidefelt
+ Hovedmal
+ Hurtigguide til Umbraco sine maltagger
+ Mal
+
+
+ Sett inn element
+ Velg layout
+ Legg til rad
+ Legg til innhold
+ Slipp innhold
+ Raden har tilpasset design
+
+ Innholdstypen er ikke tillatt her
+ Innholdstypen er tillatt her
+
+ Klikk for å bygge inn
+ Klikk for å sette inn et bilde
+ Bildetekst...
+ Skriv her...
+
+ Rutenettoppsett
+ Et oppsett er det overordnede arbeidsområdet til ditt rutenett - du vil typisk kun behøve ett eller to
+ Legg til rutenettoppsett
+ Juster oppsettet ved å konfigurere kolonnebredder og legge til ytterligere seksjoner
+ Radkonfigurasjoner
+ Rader er forhåndsdefinerte celler arrangert vannrett
+ Legg til radkonfigurasjon
+ Juster raden ved å sette cellebredder og legge til flere celler
+
+ Kolonner
+ Totalt antall kolonner i rutenettet
+
+ Innstillinger
+ Konfigurer hvilke innstillinger brukeren kan endre
+
+ Stiler
+ Konfigurer hvilke stiler redaktørene kan endre
+
+ Innstillingene lagres kun når konfigurasjonen er gyldig
+
+ Tillatt alle editorer
+ Tillat alle radkonfigurasjoner
+ Bruk som standard
+ Velg ekstra
+ Velg standard
+ er lagt til
+
+
+ Alternativt felt
+ Alternativ tekst
+ Store/små bokstaver
+ Encoding
+ Felt som skal settes inn
+ Konverter linjeskift
+ Erstatter et linjeskift med htmltaggen <br>
+ Egendefinerte felt
+ Ja, kun dato
+ Formatter som dato
+ HTML koding
+ Formater spesialtegn med tilsvarende HTML-tegn.
+ Denne teksten vil settes inn etter verdien av feltet
+ Denne teksten vil settes inn før verdien av feltet
+ Små bokstaver
+ Ingen
+ Sett inn etter felt
+ Sett inn før felt
+ Rekursivt
+ Fjern paragraftagger
+ Fjerner eventuelle <P> rundt teksten
+ Standardfelter
+ Store bokstaver
+ URL koding
+ Dersom innholdet av feltene skal sendes til en URL skal spesialtegn formatteres
+ Denne teksten vil benyttes dersom feltene over er tomme
+ Dette feltet vil benyttes dersom feltet over er tomt
+ Ja, med klokkeslett. Dato/tid separator:
+
+
+ Oppgaver satt til deg
+ som du er tildelt. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]>
+ Lukk oppgave
+ Oversettelses detaljer
+ Last ned all oversettelsesoppgaver som XML
+ Last ned XML
+ Last ned XML DTD
+ Felt
+ Inkluder undersider
+
+ [%0%] Oversettingsoppgave for %1%
+ Ingen oversettelses-bruker funnet. Vennligst opprett en oversettelses-bruker før du begynner å sende innhold til oversetting
+ Oppgaver opprettet av deg
+ opprettet av deg. For å se en detaljert visning inkludert kommentarer, klikk på "Detaljer" eller navnet på siden. Du kan også laste ned siden som XML direkte ved å klikke på linken "Last ned XML". For å lukke en oversettelsesoppgave, vennligst gå til detaljvisningen og klikk på "Lukk" knappen.]]>
+ Siden '%0%' har blitt sendt til oversetting
+ Send til oversetting
+ Tildelt av
+ Oppgave åpnet
+ Antall ord
+ Oversett til
+ Oversetting fullført.
+ Du kan forhåndsvise sidene du nettopp har oversatt ved å klikke nedenfor. Hvis den originale siden finnes, vil du få en sammenligning av sidene.
+ Oversetting mislykkes, XML filen kan være korrupt
+ Alternativer for oversetting
+ Oversetter
+ Last opp XML med oversettelse
+
+
+ Hurtigbufferleser
+ Papirkurv
+ Opprettede pakker
+ Datatyper
+ Ordbok
+ Installerte pakker
+ Installer utseende
+ Installer startpakke
+ Språk
+ Installer lokal pakke
+ Makroer
+ Mediatyper
+ Medlemmer
+ Medlemsgrupper
+ Roller
+ Medlemstyper
+ Dokumenttyper
+ Pakker
+ Pakker
+ Python Filer
+ Installer fra pakkeregister
+ Installer Runway
+ Runway moduler
+ Skriptfiler
+ Skript
+ Stiler
+ Maler
+ XSLT Filer
+ Analytics
+
+
+ Ny oppdatering er klar
+ %0% er klar, klikk her for å laste ned
+ Ingen forbindelse til server
+ Kunne ikke sjekke etter ny oppdatering. Se trace for mere info.
+
+
+ Administrator
+ Kategorifelt
+ Bytt passord
+ Nytt passord
+ Bekreft nytt passord
+ Du kan endre passordet til Umbraco ved å fylle ut skjemaet under og klikke "Bytt passord" knappen.
+ Innholdskanal
+ Beskrivelsesfelt
+ Deaktiver bruker
+ Dokumenttype
+ Redaktør
+ Utdragsfelt
+ Språk
+ Brukernavn
+ Øverste nivå i Media
+ Moduler
+ Deaktiver tilgang til Umbraco
+ Passord
+ Nullstill passord
+ Passordet er endret
+ Bekreft nytt passord
+ Nytt passord
+ Nytt passord kan ikke være blankt
+ Gjeldende passord
+ Feil passord
+ Nytt og bekreftet passord må være like
+ Nytt og bekreftet passord må være like
+ Overskriv tillatelser på undernoder
+ Du redigerer for øyeblikket tillatelser for sidene:
+ Velg sider for å redigere deres tillatelser
+ Søk i alle undersider
+ Startnode
+ Navn
+ Brukertillatelser
+ Brukertype
+ Brukertyper
+ Forfatter
+ Oversetter
+ Endre
+ Din profil
+ Din historikk
+ Sesjonen utløper om
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Config/Lang/nl.xml b/WebCms/Umbraco/Config/Lang/nl.xml
new file mode 100644
index 0000000..9ce9959
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/nl.xml
@@ -0,0 +1,1039 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Beheer domeinnamen
+ Documentgeschiedenis
+ Node bekijken
+ Wijzig document type
+ Kopiëren
+ Nieuw
+ Nieuwe package
+ Verwijderen
+ Uitschakelen
+ Prullenbak leegmaken
+ Documenttype exporteren
+ Documenttype importeren
+ Package importeren
+ Aanpassen in Canvas
+ Afsluiten
+ Verplaatsen
+ Meldingen
+ Publieke toegang
+ Publiceren
+ Depubliceren
+ Nodes opnieuw inladen
+ Herpubliceer de site
+ Rechten
+ Vorige versies
+ Klaar voor publicatie
+ Klaar voor vertalen
+ Sorteren
+ Klaar voor publicatie
+ Vertalen
+ Bijwerken
+ Standaardwaarde
+
+
+ Permission denied.
+ Nieuw domein toevoegen
+ verwijderen
+ Ongeldige node.
+ Ongeldig domeinformaat.
+ Domein is reeds aanwezig.
+ Domein
+ Taal
+ Nieuw domein '%0%' is aangemaakt
+ Domein '%0%' is verwijderd
+ Domein '%0' is al aanwezig
+ Domein '%0%' is bijgewerkt
+ Bewerk huidige domeinen
+ Zgn. 'one-level' paden in domeinen worden ondersteund, bijv. "example.com/en". Echter, ze
+ zouden moeten worden vermeden. Gebruik bij voorkeur de cultuurinstelling hierboven.]]>
+ Overerven
+ Cultuur
+ of erf de cultuur over van de ouder nodes. Zal ook van toepassing
+ zijn op de huidige node, tenzij een domein hieronder ook van toepassing is.]]>
+ Domeinen
+
+
+ Tonen voor
+
+
+ Selecteren
+ Selecteer huidige map
+ Doe iets anders
+
+ Vet
+ Paragraaf uitspringen
+ Voeg formulierveld in
+ Voeg grafische titel in
+ Wijzig Html
+ Paragraaf inspringen
+ Cursief
+ Centreren
+ Links Uitlijnen
+ Rechts Uitlijnen
+ Link Invoegen
+ Lokale link invoegen (anker)
+ Opsomming
+ Nummering
+ Macro invoegen
+ Afbeelding invoegen
+ Relaties wijzigen
+ Terug naar overzicht
+ Opslaan
+ Opslaan en publiceren
+ Opslaan en verzenden voor goedkeuring
+ voorbeeld bekijken
+ Voorbeeld bekijken is uitgeschakeld omdat er geen template is geselecteerd
+ Stijl kiezen
+ Stijlen tonen
+ Tabel invoegen
+
+
+ Om het documenttype voor de geselecteerde inhoud te wijzigen, selecteert u eerst uit de lijst van geldige types voor deze locatie.
+ Bevestig en/of wijzig vervolgens de toewijzing van eigenschappen van het huidige naar het nieuwe type en klik op Opslaan.
+ De inhoud is opnieuw gepubliceerd.
+ Huidige eigenschap
+ Huidig type
+ Het documenttype kan niet worden gewijzigd, omdat er geen alternatieven geldig zijn voor deze locatie.
+ Documenttype gewijzigd
+ Eigenschappen toewijzen
+ Toewijzen aan eigenschap
+ Nieuw sjabloon
+ Nieuw type
+ geen
+ Inhoud
+ Selecteer nieuw documenttype
+ Het documenttype van de geselecteerde inhoud is succesvol gewijzigd naar [new type] en de volgende eigenschappen zijn toegekend:
+ aan
+ Toewijzen van eigenschappen kan niet worden afgerond, omdat één of meer eigenschappen meer dan één toewijzing hebben.
+ Alleen alternatieve types geldig voor de huidige locatie worden weergegeven.
+
+
+ Is gepubliceerd
+ Over deze pagina
+ Alternatieve link
+ (hoe zou jij de foto beschrijven via de telefoon)
+ Alternatieve links
+ Klik om dit item te wijzigen
+ Aangemaakt door
+ Oorspronkelijke auteur
+ Bijgewerkt door
+ Aangemaakt op
+ Datum/tijd waarop dit document is aangemaakt
+ Documenttype
+ Aanpassen
+ Verloopt op
+ Dit item is gewijzigd na publicatie
+ Dit item is niet gepubliceerd
+ Laatst gepubliceerd op
+ Nog geen items om weer te geven.
+ Mediatype
+ Link naar media item(s)
+ Ledengroep
+ Rol
+ Ledentype
+ Geen datum gekozen
+ Pagina Titel
+ Eigenschappen
+ Dit document is gepubliceerd maar niet zichtbaar omdat de bovenliggende node '%0%' niet gepubliceerd is
+ Oeps: dit document is gepubliceerd, maar het is niet in de cache (interne serverfout)
+ Publiceren
+ Publicatiestatus
+ Publiceren op
+ Depubliceren op
+ Verwijderdatum
+ De sorteervolgorde is gewijzigd
+ Om nodes te sorteren, sleep de nodes of klik op één van de kolomtitels. Je kan meerdere nodes tegelijk selecteren door de "shift"- of "control"knop in te drukken tijdens het selecteren.
+ Statistieken
+ Titel (optioneel)
+ Alternatieve tekst (optioneel)
+ Type
+ Depubliceren
+ Laatst gewijzigd
+ Date/time this document was edited
+ Bestand(en) verwijderen
+ Link naar het document
+ Lid van groep(en)
+ Geen lid van groep(en)
+
+ Kinderen
+ Doel
+
+
+ Klik om te uploaden
+ Plaats je bestanden hier...
+
+
+ Waar wil je de nieuwe %0% aanmaken?
+ Aanmaken onder
+ Kies een type en een titel
+
+ "Documenttypes" strong>.]]>
+
+ "Mediatypes".]]>
+
+
+ Open je website
+ - Verbergen
+ Als Umbraco niet geopend wordt dan moet je misschien popups toelaten voor deze site.
+ is geopend in een nieuw venster
+ Herstarten
+ Bezoek
+ Welkom
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Naam
+ Beheer domeinnamen
+ Sluit dit venster
+ Weet je zeker dat je dit wilt verwijderen
+ Weet je zeker dat je dit wilt uitschakelen
+ Vink aub dit keuzevak aan om het verwijderen van %0% item(s) te bevestigen
+ Weet je het zeker?
+ Weet je het zeker?
+ Knippen
+ Pas woordenboekitem aan
+ Taal aanpassen
+ Lokale link invoegen
+ Karakter invoegen
+ Voeg grafische titel in
+ Afbeelding invoegen
+ Link invoegen
+ Klik om een Macro toe te voegen
+ Tabel invoegen
+ Laatst aangepast op
+ Link
+ Interne link:
+ Plaats een hekje (“#”) voor voor interne links.
+ In nieuw venster openen?
+ Macro Settings
+ Deze macro heeft geen eigenschappen die u kunt bewerken
+ Plakken
+ Bewerk rechten voor
+ De items worden nu uit de prullenbak verwijderd. Sluit dit venster niet terwijl de actie nog niet voltooid is.
+ De prullenbak is nu leeg.
+ Als items worden verwijderd uit de prullenbak, zijn ze voorgoed verwijderd.
+ regexlib.com ondervindt momenteel prolemen waarover we geen controle hebben. Onze excuses voor het ongemak.]]>
+ Zoek naar een regular expressie om validatie aan een formulierveld toe te voegen. Voorbeeld: 'email, 'post-code' 'url'
+ Verwijder Macro
+ Verplicht veld
+ Site is opnieuw geïndexeerd
+ De site is opnieuw gepubliceerd
+ De cache zal worden vernieuwd. Alle gepubliceerde inhoud zal worden ge-update, terwijl ongepubliceerde inhoud ongepubliceerd zal blijven.
+ Aantal kolommen
+ Aantal regels
+ Plaats een placeholder id door een ID op uw placeholder te zetten kunt u inhoud plaatsen in deze template vanuit onderliggende templates,
+ door te referreren naar deze ID door gebruik te maken van een <asp:content /> element.]]>
+ Selecteer een placeholder id uit onderstaande lijst. U kunt alleen
+ Id's kiezen van de master van de huidige template..]]>
+ Klik op de afbeelding voor volledige grootte
+ Kies een item
+ Toon cache item
+
+
+
+ Cultuurnaam
+
+
+ Typ je gebruikersnaam
+ Typ je wachtwoord
+ Benoem de %0%...
+ Typ een naam...
+ Typ om te zoeken...
+ Typ om te filteren...
+ Typ om tags toe te voegen (druk op enter na elke tag)...
+
+
+
+ Toestaan op root-niveau
+ Wanneer aangevinkt dan mag dit document type aangemaakt worden op het root-niveau van content of media trees.
+ Toegelaten subnodetypes
+ Document Type Composities
+ Nieuw
+ Tab verwijderen
+ Omschrijving
+ Nieuwe tab
+ Tab
+ Miniatuur
+ Lijstweergave inschakelen
+ Stelt het content item in zodat een sorteer- en zoekbare lijstweergave van onderliggende nodes wordt getoond. De onderliggende nodes worden niet in de tree getoond
+ Huidige lijstweergave
+ De actieve data type in lijstweergave
+ Maak een aangepaste lijstweergave
+ Verwijder aangepaste lijstweergave
+
+
+ Prevalue toevoegen
+ Datebase datatype
+ Data Editor GUID
+ Render control
+ Buttons
+ Geavanceerde instellingen inschakelen voor
+ Context menu inschakelen
+ Maximum standaard grootte van afbeeldingen
+ Gerelateerde stylesheets
+ Toon label
+ Breedte en hoogte
+
+
+ Je data is opgeslagen, maar voordat je deze pagina kunt publiceren moet je eerst aan paar problemen herstellen:
+ Het wachtwoord veranderen wordt door de huidige Membership Provider niet ondersteund (EnablePasswordRetrieval moet op true staan)
+ %0% bestaat al
+ Er zijn fouten geconstateerd:
+ Er zijn fouten geconstateerd:
+ Het wachtwoord moet minstens %0% tekens lang zijn en moet minstens %1% cijfers bevatten
+ %0% moet een geheel getal zijn
+ %0% op tab %1% is een verplicht veld
+ %0% is een verplicht veld
+ %0% op tab %1% is niet in het correcte formaat
+ %0% is niet in het correcte formaat
+
+
+ Het opgegeven bestandstype is niet toegestaan door de beheerder
+ OPMERKING! Ondanks dat CodeMiror is ingeschakeld, is het uitgeschakeld in Internet Explorer omdat het niet stabiel genoeg is.
+ Zowel de alias als de naam van het nieuwe eigenschappen type moeten worden ingevuld!
+ Er is een probleem met de lees/schrijf rechten op een bestand of map
+ Vul een titel in
+ Selecteer een type
+ U wilt een afbeelding groter maken dan de originele afmetingen. Weet je zeker dat je wilt doorgaan?
+ Fout in python script
+ Het python script is niet opgeslagen omdat het fouten bevat
+ Start node is verwijderd, neem contact op met uw systeembeheerder
+ Markeer de inhoud voordat u de stijl aanpast
+ Geen actieve stijlen beschikbaar
+ Plaats de cursor links van de twee cellen die je wilt samenvoegen
+ Je kunt een cel die is samengevoegd niet delen
+ Fout in de XSLT bron
+ De XSLT is niet opgeslagen omdat deze fout(en) bevat
+ Er is een configuratiefout bij het gegevenstype dat wordt gebruikt voor deze eigenschap. Controleer het gegevenstype
+
+
+ Over
+ Actie
+ Acties
+ Toevoegen
+ Alias
+ Weet je het zeker?
+ Rand
+ of
+ Annuleren
+ Cel marge
+ Kiezen
+ Sluiten
+ Venster sluiten
+ Opmerking
+ Bevestigen
+ Verhouding behouden
+ Doorgaan
+ Kopiëren
+ Aanmaken
+ Databank
+ Datum
+ Standaard
+ Verwijderen
+ Verwijderd
+ Verwijderen...
+ Ontwerp
+ Afmetingen
+ Beneden
+ Download
+ Aanpassen
+ Aangepast
+ Elementen
+ E-mail
+ Fout
+ Zoeken
+ Hoogte
+ Help
+ Icoon
+ Importeren
+ Binnenmarge
+ Invoegen
+ Installeren
+ Uitvullen
+ Taal
+ Lay-out
+ Bezig met laden
+ Geblokkeerd
+ Inloggen
+ Afmelden
+ Afmelden
+ Macro
+ Verplaatsen
+ Meer
+ Naam
+ Nieuw
+ Volgende
+ Nee
+ van
+ Ok
+ Openen
+ of
+ Wachtwoord
+ Pad
+ Placeholder ID
+ Een ogenblik geduld a.u.b.
+ Vorige
+ Eigenschappen
+ E-mail om formulier te ontvangen
+ Prullenbak
+ Overgebleven
+ Hernoemen
+ Vernieuw
+ Verplicht
+ Opnieuw proberen
+ Rechten
+ Zoek
+ Server
+ Tonen
+ Toon pagina bij versturen
+ Formaat
+ Sorteren
+ Submit
+ Type
+ Type om te zoeken...
+ Omhoog
+ Bijwerken
+ Upgrade
+ Upload
+ Url
+ Gebruiker
+ Gebruikersnaam
+ Waarde
+ Toon
+ Welkom...
+ Breedte
+ Ja
+ Map
+ Reorder
+ I am done reordering
+
+ Zoekresultaten
+
+
+ Achtergrondkleur
+ Vet
+ Tekstkleur
+ Lettertype
+ Tekst
+
+
+ Pagina
+
+
+ De installer kan geen connectie met de database maken.
+ De web.config kon niet worden opgeslagen. Gelieve de connectiestring handmatig aan te passen.
+ Je database is gevonden en is geïdentificeerd als
+ Database configuratie
+ installeren om de Umbraco %0% database te installeren]]>
+ Volgende om door te gaan.]]>
+ De database kon niet gevonden worden! Gelieve na te kijken of de informatie in de "connection string" van het "web.config" bestand correct is.
Om door te gaan, gelieve het "web.config" bestand aan te passen (met behulp van Visual Studio of je favoriete tekstverwerker), scroll in het bestand naar beneden, voeg de connection string voor je database toe in de key met naam "umbracoDbDSN" en sla het bestand op.
]]>
+ Gelieve contact op te nemen met je ISP indien nodig. Wanneer je installeert op een lokale computer of server, dan heb je waarschijnlijk informatie nodig van je systeembeheerder.]]>
+ Klik de upgrade knop om je database te upgraden naar Umbraco %0%
Maak je geen zorgen - er zal geen inhoud worden gewist en alles blijft gewoon werken!
]]>
+ Klik Volgende om verder te gaan.]]>
+ volgende om door te gaan]]>
+ Het wachtwoord van de default gebruiker dient veranderd te worden!]]>
+ De default gebruiker is geblokkeerd of heeft geen toegang tot Umbraco!
Geen verdere actie noodzakelijk. Klik Volgende om verder te gaan.]]>
+ Het wachtwoord van de default gebruiker is sinds installatie met succes veranderd.
Geen verdere actie noodzakelijk. Klik Volgende om verder te gaan.]]>
+ Het wachtwoord is veranderd!
+ Umbraco maakt een default gebruiker aan met login ('admin') and wachtwoord ('default'). Het is belangrijk dat dit wachtwoord wordt veranderd in iets unieks.
Deze stap controleert het password van de default gebruiker en adviseert of het veranderd dient te worden.
]]>
+ Neem een jumpstart en bekijk onze introductie videos
+
+ Nog niet geïnstalleerd.
+ Betreffende bestanden en mappen
+ Meer informatie over het instellen van machtigingen voor Umbraco vind je hier
+ Je dient ASP.NET 'modify' machtiging te geven voor de volgende bestanden/mappen
+ Je machtigingen zijn bijna perfect!
Je kunt Umbraco zonder problemen starten, maar je kunt nog geen packages installeren om volledig van Umbraco te profiteren.]]>
+ Hoe op te lossen
+ Klik hier om de tekst versie te lezen
+ video tutorial over het instellen van machtigingen voor Umbraco, of lees de tekst versie.]]>
+ Je machtigingen zijn misschien incorrect!
Je kunt Umbraco probleemloos starten, maar je kunt nog geen mappen aanmaken of packages installeren om zo volledig van Umbraco te profiteren.]]>
+ Je machtigingen zijn nog niet gereed gemaakt voor Umbraco!
Om Umbraco te starten zul je je machtigingen moeten aanpassen.]]>
+ Je machtigingen zijn perfect!
Je bent nu klaar om Umbraco te starten en om packages te installeren!]]>
+ Map probleem wordt opgelost
+ Volg deze link voor meer informatie over problemen met ASP.NET en het aanmaken van mappen
+ Machtigingen worden aangepast
+ Umbraco heeft write/modify toegang nodig op bepaalde mappen om bestanden zoals plaatjes en PDF's op te slaan. Het slaat ook tijdelijke data (ook bekend als 'de cache') op om de snelheid van je website te verbeteren.
+ Ik wil met een lege website beginnen
+ leer hoe). Je kunt er later alsnog voor kiezen om Runway te installeren. Ga dan naar de Ontwikkelaar sectie en kies Packages.]]>
+ Je hebt zojuist een blanco Umbraco platform geinstalleerd. Wat wil je nu doen?
+ Runway is geinstalleerd
+ Dit is onze lijst van aanbevolen modules. Vink de modules die je wilt installeren, of bekijk de volledige lijst modules]]>
+ Alleen aanbevolen voor gevorderde gebruikers
+ Ik wil met een eenvoudige website beginnen
+ "Runway" is een eenvoudige website die je van enkele elementaire documenttypes en templates voorziet. De installer kan Runway automatisch voor je opzetten, maar je kunt het gemakkelijk aanpassen, uitbreiden of verwijderen. Het is niet vereist en je kunt Umbraco prima zonder Runway gebruiken.
+
+Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je er voor kiest om Runway te installeren, dan kun je optioneel de bouwstenen (genaamd Runway Modules) kiezen om je Runway pagina's te verbeteren. Runway omvat: Home pagina, Getting Started pagina, Module installatie pagina. Optionele Modules: Top Navigatie, Sitemap, Contact, Gallery.
+ ]]>
+ Wat is Runway
+ Stap 1/5: Licentie aanvaarden
+ Stap 2/5: Database configureren
+ Stap 3/5: Controleren van rechten op bestanden
+ Stap 4/5: Umbraco beveiliging controleren
+ Stap 5/5: Umbraco is klaar
+ Bedankt dat je voor Umbraco hebt gekozen
+ Browse je nieuwe site Je hebt Runway geinstalleerd, dus kijk eens hoe je nieuwe site eruit ziet.]]>
+ Meer hulp en informatie Vind hulp in onze bekroonde community, blader door de documentatie of bekijk enkele gratis videos over het bouwen van een eenvoudige site, het gebruiken van packages en een overzicht van Umbraco terminologie]]>
+ Umbraco %0% is geïnstalleerd en klaar voor gebruik.
+ /web.config bestand aanpassen, en de Appsetting key UmbracoConfigurationStatus onder in het bestand veranderen naar '%0%'.]]>
+ meteen beginnen door de "Launch Umbraco" knop hieronder te klikken. Als je een beginnende Umbraco gebruiker bent, dan kun je you can find veel informatie op onze "getting started" pagina's vinden.]]>
+ Launch Umbraco Om je website te beheren open je simpelweg de Umbraco back office en begin je inhoud toe te voegen, templates en stylesheets aan te passen of nieuwe functionaliteit toe te voegen]]>
+ Verbinding met de database mislukt.
+ Umbraco versie 3
+ Umbraco versie 4
+ Bekijken
+ Umbraco %0% voor een nieuwe installatie of een upgrade van versie 3.0.
+ Dit is een bericht van uw Content Management Systeem.
+
+ ]]>
+ [%0%] Notificatie over %1% uitgevoerd op %2%
+ Notificaties
+
+
+ Kies een package op je computer door op "Bladeren" te klikken en de package te selecteren. Umbraco packages hebben meestal ".umb" of ".zip" als extensie.
+ Auteur
+ Demonstratie
+ Documentatie
+ Package meta data
+ Package naam
+ Package bevat geen inhoud
+
+ Je kunt dit package veilig verwijderen door 'verwijder package' te klikken.
+ ]]>
+ Geen upgrades beschikbaar
+ Package opties
+ Package lees mij
+ Package repository
+ Bevestig verwijderen
+ Package is verwijderd
+ De package is succesvol verwijderd
+ Verwijder package
+
+ Waarschuwing: alle documenten, media etc, die afhankelijk zijn van de items die je verwijderd, zullen niet meer werken en kan leiden tot een instabiele installatie,
+ wees dus voorzichtig met verwijderen. Als je niet zeker bent, neem dan contact op met de auteur van de package.
+ ]]>
+ Download update uit de repository
+ Upgrade package
+ Upgrade instructies
+ Er is een upgrade beschikbaar voor deze package. Je kunt het direct downloaden uit de Umbraco package repository.
+ Package versie
+ Package versiehistorie
+ Bekijk de package website
+
+
+ Plakken met alle opmaak (Niet aanbevolen)
+ De tekst die je probeert te plakken bevat speciale karakters en/of opmaak. Dit kan veroorzaakt worden doordat de tekst vanuit Microsoft Word is gekopieerd. Umbraco kan deze speciale karakters en formattering automatisch verwijderen zodat de geplakte tekst geschikt is voor het web.
+ Plakken als ruwe tekst en alle opmaak verwijderen
+ Plakken, en verwijder de opmaak (aanbevolen)
+
+
+ Geavanceerd: Beveilig door de Member Groups te seecteren die toegang hebben op de pagina
+ gebruik makend van Umbraco's member groups.]]>
+ role-based authentication.]]>
+ Error Pagina
+ Gebruikt om te tonen als een gebruiker is ingelogd, maar geen rechten heeft om de pagina te bekijken
+ Hoe wil je de pagina beveiligen?
+ %0% is nu beveiligd
+ Beveiliging verwijderd van %0%
+ Login Pagina
+ Kies de pagina met het login-formulier
+ Verwijder beveiliging
+ Kies de pagina's die het login-formulier en de error-berichten bevatten
+ Kies de roles wie toegang hebben tot deze pagina
+ Geef de gebruikersnaam en wachtwoord voor deze pagina
+ Eenvoudig: Beveilig door middel van gebruikersnaam en wachtwoord
+ Als je eenvoudige beveiliging wilt gebruiken met behulp van een enkele gebruikersnaam en wachtwoord
+
+
+
+
+
+
+
+ Inclusief ongepubliceerde kinderen
+ Publicatie in uitvoering - even geduld...
+ %0% van %1% pagina’s zijn gepubliceerd...
+ %0% is gepubliceerd
+ %0% en onderliggende pagina’s zijn gepubliceerd
+ Publiceer %0% en alle kinderen
+ ok om %0% te publiceren en de wijzigingen zichtbaar te maken voor bezoekers.
+ Je kunt deze pagina publiceren en alle onderliggende sub-pagina's door publiceer alle kinderen aan te vinken hieronder.
+ ]]>
+
+
+ Je hebt geen goedgekeurde kleuren geconfigureerd
+
+
+ Externe link toevoegen
+ Interne link toevoegen
+ Toevoegen
+ Bijschrift
+ Interne pagina
+ URL
+ Verplaats omlaag
+ Verplaats omhoog
+ Open in nieuw venster
+ Verwijder link
+
+
+ Reset
+
+
+ Huidige versie
+ Rode tekst wordt niet getoond in de geselecteerde versie , groen betekent toegevoegd]]>
+ Document is teruggezet
+ Hiermee wordt de geselecteerde versie als html getoond, als u de verschillen tussen de 2 versies tegelijk wilt zien, gebruik dan de diff view
+ Terugzetten naar
+ Selecteer versie
+ Bekijk
+
+
+ Bewerk script-bestand
+
+
+ Concierge
+ Inhoud
+ Courier
+ Ontwikkelaar
+ Umbraco configuratiewizard
+ Media
+ Leden
+ Nieuwsbrieven
+ Instellingen
+ Statistieken
+ Vertaling
+ Gebruikers
+ Umbraco Contour
+
+ Help
+ Formulieren
+ Analytics
+
+
+ Standaard template
+ Woordenboek sleutel
+ Om een bestaand documenttype te importeren, zoek het betreffende “.udt” bestand door op browse en import te klikken. (Je ziet een bevestigingspagina voordat de import start. Als het documenttype al bestaat dan wordt het bijgewerkt.)
+ Nieuwe tabtitel
+ Node type
+ Type
+ Stylesheet
+ Script
+ Stylesheet eigenschap
+ Tab
+ Tab titel
+ Tabs
+ Basis inhoudstype ingeschakeld
+ Dit inhoudstype gebruikt
+ als basis inhoudstype. Tabs van basis inhoudstypes worden niet getoond en kunnen alleen worden aangepast op het basis inhoudstype zelf
+ Geen eigenschappen gedefinieerd op dit tabblad. Klik op de link "voeg een nieuwe eigenschap" aan de bovenkant om een nieuwe eigenschap te creëren.
+ Master Document Type
+ Maak een bijbehorend template
+
+
+ Sort order
+ Creation date
+ Sorteren gereed.
+ Sleep de pagina's omhoog of omlaag om de volgorde te veranderen. Of klik op de kolom-header om alle pagina's daarop te sorteren.
+ Sluit dit venster niet tijdens het sorteren]]>
+
+
+ Publicatie werd geannuleerd door een 3rd party plug-in
+ Eigenschappen type bestaat al
+ Eigenschappen type aangemaakt
+ Data type: %1%]]>
+ Eigenschappen type verwijderd
+ Inhoudstype opgeslagen
+ Tab aangemaakt
+ Tab verwijderd
+ Tab met id: %0% verwijderd
+ Stylesheet niet opgeslagen
+ Stylesheet opgeslagen
+ Stylesheet opgeslagen zonder fouten
+ Datatype opgeslagen
+ Woordenboek item opgeslagen
+ Publicatie is mislukt omdat de bovenliggende pagina niet gepubliceerd is
+ Inhoud gepubliceerd
+ en zichtbaar op de website
+ Inhoud opgeslagen
+ Vergeet niet te publiceren om de wijzigingen zichtbaar te maken
+ Verzend voor goedkeuring
+ Verandering zijn verstuurd voor goedkeuring
+ Media opgeslagen
+ Media opgeslagen zonder fouten
+ Lid opgeslagen
+ Stijlsheet eigenschap opgeslagen
+ Stijlsheet opgeslagen
+ Template opgeslagen
+ Fout bij opslaan gebruiker (zie logboek)
+ Gebruiker opgeslagen
+ Gebruikerstype opgeslagen
+ Bestand niet opgeslagen
+ bestand kon niet worden opgeslagen. Controleer de bestandsbeveiliging
+ Bestand opgeslagen
+ Bestand opgeslagen zonder fouten
+ Taal opgeslagen
+ Python script niet opgeslagen
+ Python script kon niet worden opgeslagen door een fout
+ Python script opeslagen!
+ Geen fouten in python script!
+ Template niet opgeslagen
+ Controleer dat je geen 2 tamplates met dezelfde naam hebt
+ Template opgeslagen
+ Template opgeslagen zonder fouten!
+ XSLT niet opgeslagen
+ XSLT bevat een fout
+ XSLT kon niet worden opgeslagen, controleer de bestandsbeveiliging
+ XSLT opgeslagen
+ Geen fouten in de XSLT!
+ Inhoud gedepubliceerd
+ Partial view opgeslagen
+ Partial view opgeslagen zonder fouten!
+ Partial view niet opgeslagen
+ Er is een fout opgetreden bij het opslaan van het bestand.
+
+
+ Gebruik CSS syntax bijv: h1, .redHeader, .blueTex
+ Stijlsheet aanpassen
+ Bewerk stylesheet eigenschap
+ Naam waarmee de stijl in de editor te kiezen is
+ Voorbeeld
+ Stijlen
+
+
+ Template aanpassen
+ Invoegen inhoudsgebied
+ Invoegen inhoudsgebied placeholder
+ Invoegen dictionary item
+ Invoegen Macro
+ Invoegen Umbraco page field
+ Basis sjabloon
+ Quick Guide voor Umbraco template tags
+ Sjabloon
+
+
+ Item toevoegen
+ Choose a layout
+ Een rij aan de lay-out toevoegen
+ teken onderaan en voeg je eerste item toe]]>
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Klik om een item te embedden
+ Klik om een afbeelding in te voegen
+ Afbeelding ondertitel...
+ Typ hier......
+ Grid lay-outs
+ Lay-outs zijn het globale werkgebied voor de grid editor. Je hebt meestal maar één of twee verschillende lay-outs nodig
+ Een grid layout toevoegen
+ De lay-out aanpassen door de kolombreedte aan te passen en extra kolommen toe te voegen
+
+ Rijconfiguratie
+ Rijen zijn voorgedefinieerde cellen die horizontaal zijn gerangschikt
+ Een rijconfiguratie toevoegen
+ De rijconfiguratie aanpassen door de breedte van de cel in te stellen en extra cellen toe te voegen
+
+ Kolommen
+ Het totaal aantal gecombineerde kolommen in de grid layout
+
+ Instellingen
+ Configureren welke instellingen de editors kunnen aanpassen
+
+
+ Styles
+ Configureren welke stijlen de editors kunnen aanpassen
+
+ De instellingen worden enkel bewaard indien de ingevoerde Json geldig is
+
+ Alle editors toelaten
+ Alle rijconfiguraties toelaten
+
+
+ Alternatief veld
+ Alternatieve tekst
+ Kapitalisatie
+ Encoding
+ Selecteer veld
+ Converteer regelafbreking
+ ]]>
+ Aangepaste velden
+ Ja, alleen datum
+ Opmaken als datum
+ HTML-encoderen
+ Speciale karakters worden geëncodeerd naar HTML.
+ Zal worden ingevoegd na de veld waarde
+ Zal worden ingevoegd voor de veld waarde
+ Kleine letters
+ Geen
+ Invoegen na veld
+ Invoegen voor veld
+ Recursief
+ Verwijder paragraaf tags
+ tags aan het begin en einde van de tekst worden verwijderd]]>
+ Standaard velden
+ Hoofdletters
+ URL-encoderen
+ Speciale karakters in URL's worden geëncodeerd
+ Zal alleen worden gebruikt waneer de bovenstaande veld waardes leeg zijn
+ Dit veld zal alleen worden gebruikt als het primaire veld leeg is
+ Ja, met tijd. Scheidingsteken:
+
+
+ Taken aan jou toegewezen
+
+ Sluit taak
+ Details van vertaling
+ Download alle vertalingstaken als xml
+ Download xml
+ Download xml DTD
+ Velden
+ Inclusief onderliggende pagina's
+
+ [%0%] Vertaalopdracht voor %1%
+ Geen vertaal-gebruikers gevonden. Maak eerst een vertaal-gebruiker aan voordat je pagina's voor vertaling verstuurd
+ Taken aangemaakt door jou
+ die je aanmaakte. Om een detailweergave met opmerkingen te zien, klik op "Detail" of op de paginanaam. Je kan ook de pagina in XML-formaat downloaden door op de "Download XML"-link te klikken. Om een vertalingstaak te sluiten, klik je op de "Sluiten"-knop in detailweergave.]]>
+ De pagina '%0%' is verstuurd voor vertaling
+ Stuur voor vertaling
+ Toegewezen door
+ Taak geopend
+ Totaal aantal woorden
+ Vertaal naar
+ Vertaling voltooid.
+ Je kan een voorbeeld van vertaalde pagina's bekijken door hieronder te klikken. Als de originele pagina gevonden werd, wordt een vergelijking van beide pagina's getoond.
+ Vertalen niet gelukt, het XML-bestand is mogelijk beschadigd.
+ Vertalingsopties
+ Vertaler
+ Vertaald XML-document uploaden
+
+
+ Cachebrowser
+ Prullenbak
+ Gemaakte packages
+ Datatypes
+ Woordenboek
+ Geïnstalleerde packages
+ Installeer skin
+ Installeer starter kit
+ Talen
+ Installeer een lokale package
+ Macro's
+ Mediatypes
+ Leden
+ Ledengroepen
+ Rollen
+ Ledentypes
+ Documenttypes
+ Packages
+ Packages
+ Python-bestanden
+ Installeer uit repository
+ Installeer Runway
+ Runway modules
+ Script bestanden
+ Scripts
+ Stylesheets
+ Sjablonen
+ XSLT Bestanden
+
+
+ Nieuwe update beschikbaar
+ %0% is gereed, klik hier om te downloaden
+ Er is geen verbinding met de server
+ Er is een fout opgetreden bij het zoeken naar een update. Bekijk de trace-stack voor verdere informatie.
+
+
+ Beheerders
+ Categorieveld
+ Verander je wachtwoord
+ Wijzig je wachtwoord
+ Bevestig nieuw password
+ Je kunt je wachtwoord veranderen door onderstaan formulier in te vullen en op de knop 'Verander wachtwoord' te klikken
+ Inhoudskanaal
+ Omschrijving
+ Geblokkeerde gebruiker
+ Documenttype
+ Redacteur
+ Samenvattingsveld
+ Taal
+ Loginnaam
+ Startnode in Mediabibliotheek
+ Secties
+ Blokkeer Umbraco toegang
+ Wachtwoord
+ Reset wachtwoord
+ Je wachtwoord is veranderd!
+ Herhaal nieuwe wachtwoord
+ Voer nieuwe wachtwoord in
+ Je nieuwe wachtwoord mag niet leeg zijn!
+ Huidig wachtwoord
+ Ongeldig huidig wachtwoord
+ Beide wachtwoorden waren niet hetzelfde. Probeer opnieuw!
+ Beide wachtwoorden zijn niet hetzelfde!
+ Vervang rechten op de subnodes
+ U bent momenteel rechten aan het aanpassen voor volgende pagina's:
+ Selecteer pagina's om hun rechten aan te passen
+ Doorzoek alle subnodes
+ Startnode in Content
+ Gebruikersnaam
+ Gebruikersrechten
+ Gebruikerstype
+ Gebruikerstypes
+ Auteur
+ Wijzig
+
+ Je profiel
+ Je recente historie
+ Sessie verloopt over
+
+
diff --git a/WebCms/Umbraco/Config/Lang/pl.xml b/WebCms/Umbraco/Config/Lang/pl.xml
new file mode 100644
index 0000000..11f7f35
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/pl.xml
@@ -0,0 +1,820 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Zarządzanie hostami
+ Historia zmian
+ Przeglądaj węzeł
+ Kopiuj
+ Utwórz
+ Stwórz zbiór
+ Usuń
+ Deaktywuj
+ Opróżnij kosz
+ Eksportuj typ dokumentu
+ TRANSLATE ME: 'Export to .NET'
+ TRANSLATE ME: 'Export to .NET'
+ Importuj typ dokumentu
+ Importuj zbiór
+ Edytuj na stronie
+ Wyjście
+ Przenieś
+ Powiadomienia
+ Publiczny dostęp
+ Opublikuj
+ Odśwież węzeł
+ Opublikuj ponownie całą stronę
+ Uprawnienia
+ Cofnij
+ Wyślij do publikacji
+ Wyślij do tłumaczenia
+ Sortuj
+ Zapisz do opublikowania
+ Przetłumacz
+ Aktualizuj
+
+
+ Dodaj nową domenę
+ Domena
+ Domena '%0%' została utworzona
+ Domena '%0%' została skasowana
+ Domena '%0%' jest aktualnie przypisana
+ np.: yourdomain.com, www.yourdomain.com
+ Domena '%0%' została zaktualizowana
+ Edycja aktualnych domen
+
+
+ Wyświetlane dla
+
+
+ Pogrubienie
+ Zmniejsz wcięcie
+ Wstaw z pola
+ Wstaw graficzną linię nagłówka
+ Podgląd HTML
+ Zwiększ wcięcie
+ Kursywa
+ Wyśrodkuj
+ Wyrównaj do lewej
+ Wyrównaj do prawej
+ Wstaw link
+ Wstaw link wewnętrzny
+ Wypunktowanie
+ Numerowanie
+ Wstawianie makra
+ Wstawianie obrazka
+ Edycja relacji
+ Zapisz
+ Zapisz i publikuj
+ Zapisz i wyślij do sprawdzenia
+ Podgląd
+ Wybierz styl
+ Pokaż style
+ Wstaw tabelę
+
+
+ O tej stronie
+ Link alternatywny
+ (jakbyś opisał obrazek nad telefonem)
+ Alternatywne linki
+ Kliknij, aby edytować ten element
+ Utworzone przez
+ Data utworzenia
+ Rodzaj dokumentu
+ Edytowanie
+ Usuń w
+ Ten element został zmieniony po publikacji
+ Element nie jest opublikowany
+ Opublikowane
+ Typ mediów
+ Członek grupy
+ Rola
+ Członek typ
+ Brak daty
+ Tytuł strony
+ Właściwości
+ Ten dokument jest opublikowany, ale jest niewidoczny, ponieważ jego rodzic '%0%' nie jest opublikowany
+ Publikuj
+ Status
+ Opublikuj
+ Data usunięcia
+ Porządek się zmienił
+ Aby posortować gałęzie, poprostu przeciągnij gałąż lub kliknij na jednym z nagłówków kolumn. Możesz wybrać kilka gałęzi poprzez przytrzymanie klawisza "shift" lub "control" podczas zaznaczania
+ Statystyki
+ Tytuł (opcjonalny)
+ Typ
+ Cofnij publikację
+ Ostatnio edytowany
+ Usuń plik
+ Link do dokumentu
+
+
+ Gdzie chcesz stworzyć nowy %0%?
+ Utwórz w
+ Wybierz rodzaj oraz tytuł
+
+
+ Przeglądaj swoją stronę
+ TRANSLATE ME: '- Hide'
+ Jeśli Umbraco się nie otwiera, prawdopodbnie musisz zezwolić tej stronie na otwieranie wyskakujących okienek
+ zostało otwarte w nowym oknie
+ Restartuj
+ Odwiedź
+ Witaj
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Nazwa
+ Zarządzaj nazwami hostów
+ Zamknij to okno
+ Jesteś pewny, że chcesz usunąć
+ Jesteś pewny, że chcesz wyłączyć
+ Proszę zaznaczyć, aby potwierdzić usunięcie %0% elementów.
+ Jesteś pewny?
+ Jesteś pewny?
+ TRANSLATE ME: 'Cut'
+ Edytuj element słownika
+ Edytuj język
+ Wstaw link wewnętrzny
+ Wstaw znak
+ Wstaw graficzny nagłówek
+ Wstaw zdjęcie
+ Wstaw link
+ Wstaw makro
+ Wstaw tabelę
+ Ostatnio edytowane
+ Link
+ Link wewnętrzny:
+ Kiedy używasz odnośników lokalnych, wstaw znak "#" na początku linku
+ Otworzyć w nowym oknie?
+ TRANSLATE ME: 'Macro Settings'
+ To makro nie posiada żadnych właściwości, które można edytować
+ Wklej
+ Edytuj Uprawnienia dla
+ Zawartość kosza jest teraz usuwana. Proszę nie zamykać tego okna do momentu zakończenia procesu.
+ Zawartość kosza została usunięta
+ Usunięcie elementów z kosza powoduje ich trwałe i nieodwracalne skasowanie
+ regexlib.com aktulanie nie jest dostępny, na co nie mamy wpływu. Bardzo przepraszamy za te utrudnienia.]]>
+ Przeszukaj dla wyrażeń regularnych aby dodać regułę sprawdzającą do formularza. Np. 'email' 'url'
+ TRANSLATE ME: 'Remove Macro'
+ Pole wymagane
+ Strona została przeindeksowana
+ Cache strony zostało odświeżone. Cała opublikowana zawartość jest teraz aktualna. Natomiast cała nieopublikowana zawartość ciągle nie jest widoczna
+ Cache strony zostanie odświeżone. Cała zawartość opublikowana będzie aktulana, lecz nieopublikowana zawartość pozostanie niewidoczna
+ Liczba kolumn
+ Liczba wierszy
+ Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, ustawiając dowiązanie do tego ID na elemencie <asp:treści />]]>
+ Wybierz zastępczy id z poniższej listy. Możesz wybierać tylko spośród id na szablonie nadrzędnym tego formularza.]]>
+ Kliknij na obrazie, aby zobaczyć je w pełnym rozmiarze
+ Wybierz element
+ Podgląd elementów Cache
+
+
+ %0%' poniżej.
+Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]>
+ Nazwa języka
+
+
+ Dozwolone węzły pochodne
+ Stwórz
+ Usuń zakładkę
+ Opis
+ Nowa zakładka
+ Zakładka
+ Miniatura
+
+
+ Dodaj wartość
+ Typ bazydanych
+ TRANSLATE ME: 'Data Editor GUID'
+ Renderuj kontrolkę
+ Przyciski
+ Włącz ustawienia zaawansowane dla
+ Włącz menu podręczne
+ Maksymalny dozwolony rozmiar wstawianego obrazu
+ Powiązane arkusze stylów
+ Pokaż etykietę
+ Szerokość i wysokość
+
+
+ Dane zostały zapisane, lecz wystąpiły błędy które musisz poprawić przed publikacją strony:
+ Bieżący dostawca Membership nie obsługuje zmiany hasła (EnablePasswordRetrieval musi mieć wartość true)
+ TRANSLATE ME: '%0% already exists'
+ Wystąpiły błędy:
+ Wystąpiły błędy:
+ Hasło powinno mieć minimum %0% znaków, i zawierać co najmniej %1% niealfanumeryczny znak
+ %0% musi być liczbą całkowitą
+ %0% (%1%) to pole wymagane
+ %0% to pole wymagane
+ %0% w %1% nie jest w odpowiednim formacie
+ %0% nie jest w odpowiednim formacie
+
+
+ TRANSLATE ME: 'NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough.'
+ Proszę uzupełnij zarówno alias jak i nazwę dla nowego typu właściwości
+ Wystąpił problem podczas zapisu/odczytu wymaganego pliku lub folderu
+ Proszę podać tytuł
+ Proszę wybrać typ
+ Chcesz utworzyć obraz większy niż rozmiar oryginalny. Czy na pewno chcesz kontynuować?
+ Błąd w skrypcie python
+ Skrypt python nie został zapisany, ponieważ zawiera błędy
+ Węzeł początkowy usunięto, proszę skontaktować się z administratorem
+ Proszę zaznaczyć zawartość przed zmianą stylu
+ Brak dostępnych aktywnych stylów
+ Proszę ustaw kursor po lewej stronie dwóch cel które chcesz połączyć
+ Nie możesz podzielić komórki, która nie była wcześniej połączona.
+ Błąd w źródle XSLT
+ Plik XSLT nie został zapisany, ponieważ wystąpiły błędy
+
+
+ O...
+ Akcja
+ Dodaj
+ Alias
+ Czy jesteś pewny?
+ Ramka
+ lub
+ Anuluj
+ Marginesy komórki
+ Wybierz
+ Zamknij
+ Zamknij okno
+ Komentarz
+ Potwierdzenie
+ Zachowaj proporcje
+ Kontynuuj
+ Kopiuj
+ Utwórz
+ Baza danych
+ Data
+ Domyślne
+ Usuń
+ Usunięto
+ Usuwanie...
+ Wygląd
+ Rozmiary
+ Dół
+ Pobierz
+ Edytuj
+ Edytowane
+ Elementy
+ Email
+ Błąd
+ Znajdź
+ Wysokość
+ Pomoc
+ Ikona
+ Importuj
+ Margines wewnętrzny
+ Wstaw
+ Instaluj
+ Wyrównaj
+ Język
+ układ
+ Ładowanie
+ TRANSLATE ME: 'Locked'
+ Zaloguj
+ Wyloguj
+ Wyloguj
+ Makro
+ Przenieś
+ Nazwa
+ Nowy
+ Dalej
+ Nie
+ z
+ OK
+ Otwórz
+ lub
+ Hasło
+ Scieżka
+ Zastępczy ID
+ Proszę zaczekać...
+ Poprzedni
+ Właściwości
+ E-mail aby otrzymywać dane z formularzy
+ Kosz
+ Pozostało
+ Zmień nazwę
+ TRANSLATE ME: 'Renew'
+ Ponów próbę
+ Uprawnienia
+ Szukaj
+ Serwer
+ Pokaż
+ Pokaż stronę "wyślij"
+ Rozmiar
+ Sortuj
+ Submit
+ Typ
+ Szukaj
+ W górę
+ Update
+ Aktualizacja
+ Wyślij plik
+ Url
+ Użytkownik
+ Login
+ Wartość
+ Widok
+ Witaj...
+ Szerokość
+ Tak
+ Reorder
+ I am done reordering
+
+
+ Kolor tła
+ Pogrubienie
+ Kolor tekstu
+ Czcionka
+ Tekst
+
+
+ Strona
+
+
+ Instalator nie mógł połączyć się z bazą danych.
+ Nie udało się zapisać pliku web.config. Zmodyfikuj parametry połączenia ręcznie.
+ Twoja baza danych została znaleziona i zidentyfikowana jako
+ Konfiguracja bazy danych
+ instaluj aby zainstalować bazę danych Umbraco %0%]]>
+ Dalej aby kontynuować.]]>
+ Nie odnaleziono bazy danych! Sprawdź czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.
Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "umbracoDbDSN" i zapisz plik.
]]>
+ Skontaktuj się z Twoim dostawą usług internetowych jeśli zajdzie taka potrzeba. W przypadku instalacji na lokalnej maszynie lub serwerze możesz potrzebować pomocy administratora.]]>
+ Naciśnij przycisk aktualizuj by zaktualizować swoją bazę danych do Umbraco %0%
Bez obaw - żadne dane nie zostaną usunięte i wszystko będzie działać jak należy!
]]>
+ Naciśnij przycisk Dalej aby kontynuować.]]>
+ Dalej aby kontynuować kreatora instalacji.]]>
+ Hasło domyślnego użytkownika musi zostać zmienione!]]>
+ Konto domyślnego użytkownika została wyłączone lub nie ma on dostępu do Umbraco!
Żadne dotatkowe czynności nie są konieczne. Naciśnij Dalej aby kontynuować.]]>
+ Hasło domyślnego użytkownika zostało zmienione od czasu instalacji!
Żadne dotatkowe czynności nie są konieczne. Naciśnij Dalej aby kontynuować.]]>
+ Hasło zostało zmienione!
+ Umbraco tworzy domyślne konto użytkownika o loginie ('admin') i haśle ('default'). Jest ważne, by zmienić hasło na inne.
Ten krok sprawdzi, czy hasło domyślnego użytkownika powinno być zmienione.
]]>
+ Aby szybko wejść w świat Umbraco, obejrzyj nasze filmy wprowadzające
+ Klikając przycisk dalej (lub modyfikując klucz UmbracoConfigurationStatus w pliku web.config), akceptujesz licencję na niniejsze oprogramowanie zgodnie ze specyfikacją w poniższym polu. Zauważ, że ta dystrybucja Umbraco składa się z dwoch licencji - licencja MIT typu open source dla kodu oraz licencja "Umbraco freeware", która dotyczy interfejsu użytkownika.
+ Nie zainstalowane.
+ Zmienione pliki i foldery
+ Więcej informacji na temat ustalania pozwoleń dla Umbraco znajdziesz tutaj
+ Musisz zezwolić procesowi ASP.NET na zmianę poniższych plików/folderów
+ Twoje ustawienia uprawnień są prawie idealne!
Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane aby w pełni wykorzystać możliwości Umbraco.]]>
+ Jak to Rozwiązać
+ Kliknij tutaj, aby przeczytać wersję tekstową
+ video tutorial pokazujący jak ustawić uprawnienia dostępu do folderów dla Umbraco albo przeczytaj wersję tekstową.]]>
+ Twoje ustawienia uprawnień mogą stanowić problem! Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane aby w pełni wykorzystać możliwości Umbraco.]]>
+ Twoje ustawienia uprawnień nie są gotowe na Umbraco!
Aby Umbraco mogło działać musisz uaktualnić swoje ustawienia zabezpieczeń.]]>
+ Twoje ustawienia uprawnień są idealne!
Umbraco będzie działać bez problemów i będzie można instalować pakiety!]]>
+ Rozwiązywanie problemów z folderami
+ Kliknij ten link, aby uzyskać więcej informacji na temat problemów z ASP.NET i tworzeniem folderów.
+ Ustawianie uprawnień dostępu do folderów
+ Umbraco potrzebuje uprawnień do zapisu/odczytu pewnych katalogów w celu przechowywania plików jak obrazki i PDF'y. Umbraco przechowuje także tymczasowe dane (aka: cache) aby zwiększyć wydajność Twojej strony.
+ Chce zacząć od zera
+ dowiedz się jak) Ciągle możesz wybrać, aby zainstalować Runway w późniejszym terminie. W tym celu przejdź do sekcji Deweloper i wybierz Pakiety.]]>
+ Właśnie stworzyłeś czystą instalację platformy Umbraco. Co chcesz zrobić teraz?
+ Pakiet Runway zainstalowany pomyślnie
+ To jest nasza lista rekomendowanych modułów. Zaznacz te, które chcesz zainstalować lub wyświetl pełną listę modułów ]]>
+ Rekomendowane tylko dla doświadczonych użytkowników
+ Chce rozpocząć z prostą stroną
+ Pakiet "Runway" to prosta strona dostarczająca kilka podstawowych typów dokumentów i szablonów. Instalator może automatycznie zainstalować pakiet Runway za Ciebie, ale możesz w łatwy sposób edytować, rozszerzyć lub usunąć go. Nie jest on potrzebny i możesz doskonale używać Umbraco bez niego. Jednakże pakiet Runway oferuje łatwą podstawę bazującą na najlepszych praktych, która pozwolić Ci rozpocząć pracę w mgnieniu oka. Jeśli zdecydujesz się zainstalować pakiet Runway, możesz opcjonalnie wybrać podstawowe klocki zwane Modułami Runway, aby poprawić swoje strony. Dołączone z pakietem Runway: Strona domowa, strona Jak rozpocząć pracę, strona Instalowanie Modułów. Opcjonalne moduły:Górna nawigacja, Mapa strony, Formularz kontaktowy, Galeria. ]]>
+ Co to jest pakiet Runway
+ Krok 1/5 Akceptacja licencji
+ Krok 2/5: Konfiguracja bazy danych
+ Krok 3/5: Sprawdzanie uprawnień plików
+ Krok 4/5: Sprawdzanie zabezpieczeń Umbraco
+ Krok 5/5: Umbraco jest gotowy do pracy
+ Dziękujemy za wybór Umbraco
+ Przeglądaj swoją nową stronę Pakiet Runway został zainstalowany, zobacz zatem jak wygląda Twoja nowa strona.]]>
+ Dalsza pomoc i informacje Zaczerpnij pomocy z naszej nagrodzonej społeczności, przeglądaj dokumentację lub obejrzyj niektóre darmowe filmy o tym jak budować proste strony, jak używać pakietów i szybki przewodnik po terminologii Umbraco]]>
+ Umbraco %0% zostało zainstalowane i jest gotowe do użycia
+ plik web.config i zaktualizować klucz AppSetting o nazwie UmbracoConfigurationStatus na dole do wartości '%0%'.]]>
+ rozpocząć natychmiast klikając przycisk "Uruchom Umbraco" poniżej. Jeżeli jesteś nowy dla Umbraco znajdziesz mnóstwo materiałów na naszych stronach "jak rozpocząć".]]>
+ Uruchom Umbraco Aby zarządzać swoją stroną po prostu otwórz zaplecze Umbraco i zacznij dodawać treść, aktualizować szablony i style lub dodawaj nową funkcjonalność]]>
+ Połączenie z bazą danych nie zostało ustanowione.
+ Umbraco wersja 3
+ Umbraco wersja 4
+ Zobacz
+ Umbraco %0% dla świżej instalacji lub aktualizacji z wersji 3.0.
]]>
+ [%0%] Powiadomienie o %1% wykonane na %2%
+ Powiadomienie
+
+
+ Wskaż pakiet z twojego komputera, poprzez kliknięcie na przycisk 'Przeglądaj' i wskaż gdzie jest zapisany. Pakiety Umbraco przeważnie posiadają rozszerzenie ".umb" lub ".zip".
+ Autor
+ Demonstracja
+ Dokumentacja
+ Metadane pakietu
+ Nazwa pakietu
+ Pakiet nie zawiera żadnych elementów
+ Możesz bezpiecznie go usunąć z systemu poprzez kliknięcie na przycisku "odinstaluj pakiet"]]>
+ Nie ma dostępnych aktualizacji
+ Opcje pakietu
+ Opis pakietu
+ Repozytorium pakietu
+ Potwierdź odinstalowanie
+ Pakiet został odinstalowany
+ Pakiet został pomyślnie odinstalowany
+ Odinstaluj pakiet
+ span style="color: Red; font-weight: bold;">Uwaga: wszystkie elementy, media itp w zależności od elementów, które usuwasz przestaną działać, i mogą spowodować niestabilność systemu, więc odinstalowuj z uwagą. W przypadku problemów kontaktuj się z autorem pakietu.]]>
+ Pobierz aktualizację z repozytorium
+ Aktualizuj pakiet
+ Instrukcja aktualizacji
+ Jest dostępna aktualizacja dla tego pakietu. Możesz ją pobrać wprost z repozytorium pakietów Umbraco.
+ Wersja pakietu
+ Odwiedź stronę pakietu
+
+
+ Wklej z zachowaniem formatowania (Nie zalecane)
+ Tekst, który wklejasz zawiera specjalne znaki formatujące. Prawdopodobnie tekst pochodzi z programu Microsoft Word. Umbraco może usunąć specjalne znaki lub formatowanie automatycznie, więc skopiowana treść będzie lepiej dopasowana do wyświetlania w Internecie.
+ Wklej sam tekst, bez żadnego formatowania
+ Wklej, usuwając formatowanie (zalecane)
+
+
+ Ochrona w oparciu o role
+ użyj grup członkowskich Umbraco ]]>
+ Musisz utworzyć grupę przed użyciem uwierzytelniania opartego na rolach
+ Strona błędu
+ Używana kiedy użytkownicy są zalogowani, ale nie posiadają dostępu
+ Wybierz sposób ograniczenia dostępu do tej strony
+ %0% jest teraz zabezpieczona
+ Ze strony %0% usunięto zabezpieczenia dostępu
+ Strona logowania
+ Wybierz stronę z formularzem logowania
+ Usuń ochronę
+ Wybierz strony, które zawierają formularz logowania i komunikatów o błędach
+ Wybierz role, które mają mieć dostęp do tej strony
+ Ustaw login i hasło dla tej strony
+ Ochrona pojedynczego użytkownika
+ Jeżeli chcesz ustawić prostą ochronę używając pojedynczego loginu i hasła
+
+
+ %0% nie może zostać opublikowany, ze względu na odwołanie akcji przez rozszerzenie firm trzecich
+ Dołącz nieopublikowane węzły pochodne (dzieci)
+ Publikacja w toku - proszę czekać...
+ Opublikowano %0% z %1% stron...
+ %0% został opublikowany
+ %0% oraz podstrony zostały opublikowane
+ Publikuj %0% z wszytkimi podstronami
+ OK em>, aby publikować % 0% strong> i spowodować upublicznienie całej treści.
Możesz opublikować tą stronę wraz ze wszystkimi podstronami zaznaczając poniżej publikuj wszystkie węzły pochodne]]>
+
+
+ Dodaj link zewnętrzny
+ Dodaj link wewnętrzny
+ Dodaj
+ Opis
+ Strona wewnętrzna
+ URL
+ Przesuń w dół
+ Przesuń do góry
+ Otwórz w nowym oknie
+ Usuń link
+
+
+ Aktualna wersja
+ Red tekst nie będzie pokazany w wybranej wersji, zielony tekst został dodany]]>
+ Dokument został przywrócony
+ Tu widać wybraną wersję jako html, jeżeli chcesz zobaczyć różnicę pomiędzy 2 wersjami w tym samym czasie, użyj podglądu róznic
+ Cofnij do
+ Wybierz wersję
+ Zobacz
+
+
+ Edytuj skrypt
+
+
+ Concierge
+ Treść
+ Kurier
+ Deweloper
+ Konfigurator Umbraco
+ Media
+ Członkowie
+ Biuletyny
+ Ustawienia
+ Statystyki
+ Tłumaczenie
+ Użytkownicy
+
+
+ Domyślny szablon
+ Klucz słownika
+ By zaimportować typ dokumentu, wskaż plik ".udt" na swoim komputerze, klikając przycisk "Przeglądaj" i kliknij "Importuj" (zostaniesz poproszony o potwierdzenie w następnym kroku)
+ Nazwa nowej zakładki
+ Typ węzła
+ Typ
+ Arkusz styli
+ Właściwości arkusza styli
+ Zakładka
+ Nazwa zakładki
+ Zakładki
+
+
+ Sort order
+ Creation date
+ Sortowanie zakończone.
+ Przesuń poszczególne elementy w górę oraz w dół aż będą w odpowiedniej kolejności, lub kliknij na nagłówku kolumny aby posortować całą kolekcję elementów
+ Nie zamykaj tego okna podczas sortowania]]>
+
+
+ Publikacja została przerwana poprzez dodatek firmy trzeciej
+ Właściwość typu już istnieje
+ Właściwość typu została utworzona
+ typ danych: %1%]]>
+ Właściwość typu została usunięta
+ Zakładka została zapisana
+ Zakładkę utworzono
+ Zakładkę usunięto
+ Usunięto zakładkę o id:%0%
+ Arkusz stylów nie został zapisany
+ Arkusz stylów został zapisany
+ Arkusz stylów został zapisany bez żadnych błędów
+ Typ danych został zapisany
+ Element słownika został zapisany
+ Publikacja nie powiodła się ponieważ rodzic węzła nie jest opublikowany
+ Treść została opublikowana
+ i jest widoczna na stronie
+ Treść została zapisana
+ Pamiętaj, aby opublikować, aby zmiany były widoczne
+ Wysłano do zatwierdzenia
+ Zmiany zostały wysłane do akceptacji
+ Członek został zapisany
+ Właściwość arkusza stylów została zapisana
+ Arkusz stylów został zapisany
+ Szablon został zapisany
+ Błąd przy zapisie danych użytkownika (sprawdź log)
+ Użytkownik został zapisany
+ Plik nie został zapisany
+ Plik nie został zapisany. Sprawdź uprawnienia dostępu do pliku
+ Plik został zapisany
+ Plik został zapisany bez żadnych błędów
+ Język został zapisany
+ Skrypt python nie został zapisany
+ Wystąpiły błędy, skrypt nie może zostać zapisany
+ Skrypt python został zapisany
+ Brak błędów w skrypcie python
+ Szablon nie został zapisany
+ Proszę się upewnić że nie ma dwóch szablonów o tym samym aliasie
+ Szablon został zapisany
+ Szablon został zapisany bez żadnych błędów!
+ Nie zapisano XSLT
+ XSLT zawiera błędy
+ Nie można zapisać XSLT, sprawdź uprawnienia dostępu do pliku
+ Zapisano XSLT
+ XSLT nie zawiera błedów
+
+
+ Używaj składni CSS np.: h1, .czerwonyNaglowek, .niebieskiTekst
+ Edytuj arkusz stylów
+ Edytuj właściwość arkusza stylów
+ Nazwa dla znalezienia właściwości stylu w edytorze
+ Podgląd
+ Style
+
+
+ Edytuj szablon
+ Wstaw obszar zawartości
+ Wstaw miejsce dla obszaru zawartości
+ Wstaw element słownika
+ Wstaw makro
+ Wstaw pole strony Umbraco
+ Główny szablon
+ Szybki przewodnik po tagach szablonu Umbraco
+ Szablon
+
+
+ Choose type of content
+ Choose a layout
+ Add a row
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Click to embed
+ Click to insert image
+ Image caption...
+ Write here...
+
+ Grid Layouts
+ Layouts are the overall work area for the grid editor, usually you only need one or two different layouts
+ Add Grid Layout
+ Adjust the layout by setting column widths and adding additional sections
+ Row configurations
+ Rows are predefined cells arranged horizontally
+ Add row configuration
+ Adjust the row by setting cell widths and adding additional cells
+
+ Columns
+ Total combined number of columns in the grid layout
+
+ Settings
+ Configure what settings editors can change
+
+ Styles
+ Configure what styling editors can change
+
+ Settings will only save if the entered json configuration is valid
+
+ Allow all editors
+ Allow all row configurations
+
+
+ Pole alternatywne
+ Tekst alternatywny
+ Wielkość liter
+ Wybierz pole
+ Konwertuj złamania wiersza
+ Zamienia złamania wiersza na html-tag <br>
+ Tak, tylko data
+ Formatuj jako datę
+ Kodowanie HTML
+ Zamienia znaki specjalne na ich odpowiedniki HTML
+ Zostanie wstawione za wartością pola
+ Zostanie wstawione przed wartością pola
+ małe znaki
+ Nic
+ Wstaw za polem
+ Wstaw przed polem
+ Rekurencyjne
+ Usuń znaki paragrafu
+ Usuwa wszystkie <P> z początku i końca tekstu
+ WIELKIE LITERY
+ Kodowanie URL
+ Formatuje znaki specjalne w URLach
+ Zostanie użyte tylko wtedy gdy wartość pola jest pusta
+ To pole jest używane tylko wtedy gdy główne pole jest puste
+ Tak, z czasem. Separator:
+
+
+ Zadania przypisane dla Ciebie
+ przypisane do Ciebie. Aby zobaczyć szczegółowe informacje wraz z komentarzami, kliknij na "Szczegóły" lub po prostu na nazwę strony. Możesz również pobrać stronę jako XML poprzez kliknięcie na link "Pobierz XML". Aby zamknąć zadanie tłumaczenia, proszę w podglądzie szczegółowym kliknąć przycisk "Zamknij".]]>
+ zamknij zadanie
+ Szczegóły tłumaczenia
+ Pobierz wszystkie tłumaczenia jako XML
+ Pobierz XML
+ Pobierz XML DTD
+ Pola
+ Włączając podstrony
+ to jest automatyczny mail informujący że dokument %1% został zgłoszony jako wymagający tłumaczenia na '%5%' przez %2%. Wejdź na http://%3%/translation/details.aspx?id=%4% aby edytować. Lub zaloguj się do Umbraco aby zobaczyć wszystkie zadania do tłumaczenia http://%3%. Życzę miłego dnia! Umbraco robot]]>
+ [%0%] Tłumaczeń dla %1%
+ Nie znaleziono tłumaczy. Proszę utwórz tłumacza przed wysłaniem zawartości do tłumaczenia
+ Zadania stworzone przez Ciebie
+ stworzone przez Ciebie. Aby zobaczyć szczegółowe informacje wraz z komentarzami, kliknij na "Szczegóły" lub na nazwę strony. Możesz również pobrać stronę jako XML poprzez kliknięcie na link "Pobierz XML". Aby zamknąć zadanie tłumaczenia, proszę w podglądzie szczegółowym kliknąć przycisk "Zamknij".]]>
+ Strona '%0%' została wysłana do tłumaczenia
+ Wyślij stronę '%0%' do tłumaczenia
+ Przypisane przez
+ Zadanie otwarte
+ Liczba słów
+ Przetłumacz na
+ Tłumaczenie zakończone.
+ Możesz podglądnąć stronę, którą właśnie przetłumaczyłeś, poprzez kliknięcie poniżej. Jeżeli strona oryginalna istnieje, możesz porównać obie wersje
+ Błąd tłumaczenia, plik XML może być uszkodzony
+ Opcje tłumaczeń
+ Tłumacz
+ Załaduj przetłumaczony XML
+
+
+ Cache przeglądarki
+ Kosz
+ Utworzone pakiety
+ Typy danych
+ Słownik
+ Zainstalowane pakiety
+ TRANSLATE ME: 'Install skin'
+ TRANSLATE ME: 'Install starter kit'
+ Języki
+ Zainstaluj pakiet lokalny
+ Makra
+ Typy mediów
+ Członkowie
+ Grupy członków
+ Role
+ Typ członka
+ Typy dokumentów
+ Pakiety
+ Pakiety
+ Pliki Python
+ Zainstaluj z repozytorium
+ Zainstaluj Runway
+ Moduły Runway
+ TRANSLATE ME: 'Scripting Files'
+ Skrypty
+ Arkusze stylów
+ Szablony
+ Pliki XSLT
+
+
+ Aktualizacja jest gotowa
+ Gotowe jest %0%, kliknij tutaj aby pobrać
+ Brak połączenia z serwerem
+ Wystąpił błąd podczas sprawdzania aktualizacji. Przeglądnij trace-stack dla dalszych informacji
+
+
+ Administrator
+ Pole kategorii
+ TRANSLATE ME: 'Change Your Password'
+ TRANSLATE ME: 'You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button'
+ Zawartość
+ Opis
+ Wyłącz użytkownika
+ Typ dokumentu
+ Edytor
+ Wypis
+ Język
+ Login
+ Węzeł początkowy w bibliotece mediów
+ Sekcje
+ Wyłącz dostęp do Umbraco
+ Hasło
+ TRANSLATE ME: 'Your password has been changed!'
+ TRANSLATE ME: 'Please confirm the new password'
+ TRANSLATE ME: 'Enter your new password'
+ TRANSLATE ME: 'Your new password cannot be blank!'
+ TRANSLATE ME: 'There was a difference between the new password and the confirmed password. Please try again!'
+ TRANSLATE ME: 'The confirmed password doesn't match the new password!'
+ Zastąp prawa dostępu dla węzłów potomnych
+ Aktualnie zmieniasz uprawnienia dostępu do stron:
+ Wybierz strony, którym chcesz zmienić prawa dostępu
+ Przeszukaj wszystkie węzły potomne
+ Węzeł początkowy w treści
+ Nazwa użytkownika
+ Prawa dostępu użytkownika
+ Typ
+ Typy użytkowników
+ Pisarz
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Config/Lang/pt.xml b/WebCms/Umbraco/Config/Lang/pt.xml
new file mode 100644
index 0000000..f02415e
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/pt.xml
@@ -0,0 +1,908 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Gerenciar hostnames
+ Caminho de Auditoria
+ Navegar o Nó
+ Copiar
+ Criar
+ Criar Pacote
+ Remover
+ Desabilitar
+ Esvaziar Lixeira
+ Exportar Tipo de Documento
+ Exportar para .NET
+ Exportar para .NET
+ Importar Tipo de Documento
+ Importar Pacote
+ Editar na Tela
+ Sair
+ Mover
+ Notificações
+ Acesso público
+ Publicar
+ Recarregar nós
+ Republicar site inteiro
+ Permissões
+ Reversão
+ Enviar para Publicação
+ Enviar para Tradução
+ Classificar
+ Enviar para publicação
+ Traduzir
+ Atualizar
+
+
+ Adicionar novo Domínio
+ Domínio
+ Novo domínio '%0%' foi criado
+ Domínio '%0%' foi removido
+ Domínio '%0%' já foi designado
+ ou seja: seudominio.com, www.seudominio.com
+ Domínio '%0%' foi atualizado
+ Editar Domínios Atuais
+
+
+ Visão para
+
+
+ Negrito
+ Remover Travessão de Parágrafo
+ Inserir campo de formulário
+ Inserir manchete de gráfico
+ Editar Html
+ Travessão de Parágrafo
+ Itálico
+ Centro
+ Justificar à Esquerda
+ Justificar à Direita
+ Inserir Link
+ Inserir link local (âncora)
+ Lista de tópicos
+ Lista numérica
+ Inserir macro
+ Inserir figura
+ Editar relacionamentos
+ Salvar
+ Salvar e publicar
+ Salvar e mandar para aprovação
+ Prévia
+ Escolha estilo
+ Mostrar estilos
+ Inserir tabela
+
+
+ Sobre esta página
+ Link alternativo
+ (como você descreveria a imagem pelo telefone)
+ Links Alternativos
+ Clique para editar este item
+ Criado por
+ Criado
+ Tipo de Documento
+ Editando
+ Remover em
+ Este item foi alterado após a publicação
+ Este item não está publicado
+ Última publicação
+ Tipo de Mídia
+ Grupo do Membro
+ Função
+ Tipo de Membro
+ Nenhuma data escolhida
+ Título da Página
+ Propriedades
+ Este documento está publicado mas não está visível porque o pai '%0%' não está publicado
+ Publicar
+ Status da Publicação
+ Publicado em
+ Remover Data
+ Ordem de classificação está atualizada
+ Para classificar os nós simplesmente arraste os nós ou clique em um dos títulos de colunas. Você pode selecionar múltiplos nós ao pressionar e segurar 'shift' ou 'control' durante a seleção
+ Estatísticas
+ Título (opcional)
+ Tipo
+ Des-Publicar
+ Última edição
+ Remover arquivo
+ Link ao documento
+
+
+ Onde você quer criar seu novo(a) %0%
+ Criado em
+ Escolha um tipo e um título
+
+
+ Navegue seu site
+ - Esconder
+ Se Umbraco não estiver abrindo talvez você precise hablitar pop-ups para este site
+ foi aberto em uma nova janela
+ Reiniciar
+ Visitar
+ Bem Vindo(a)
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Nome
+ Gerenciar hostnames
+ Fechar esta janela
+ Certeza em remover
+ Certeza em desabilitar
+ Favor selecionar esta caixa para confirmar a remoção de %0% item(s)
+ Tem certeza
+ Tem certeza?
+ Cortar
+ Editar Item de Dicionário
+ Editar Linguagem
+ Inserir link local
+ Inserir charactere
+ Inserir manchete de gráfico
+ Inserir figura
+ Inserir Link
+ Inserir Macro
+ Inserir tabela
+ Última Edição
+ Link
+ Link interno:
+ Ao usar links locais insira "#" na frente do link
+ Abrir em nova janela?
+ Configurações de Macro
+ Este macro não contém nenhuma propriedade que possa ser editada
+ Colar
+ Editar Permissões para
+ Os itens na lixeira agora estão sendo removidos. Favor não fechar esta janela enquanto este processo é concluído
+ A lixeira agora está vazia
+ Quando itens são removidos da lixeira estes somem para sempre
+ regexlib.com está no momento sofrendo dificuldades dos quais não temos controle. Pedimos desculpas pela inconveniência.]]>
+ Busque por uma expressão regular para adicionar validação à um campo de formulário. Exemplo: 'email', 'zip-code' (código postal), 'url'
+ Remover Macro
+ Campo obrigatório
+ Site foi re-indexado
+ O cache do website foi atualizado. Todo conteúdo publicado está atualizado agora. No entanto, todo conteúdo não publicado ainda permanecerá invisível
+ O cache do website será atualizado. Todo conteúdo publicado será atualizado, enquanto o conteúdo que não foi publicado permanecerá invisível
+ Número de colunas
+ Número de linhas
+ Defina uma id de espaço reservado definindo uma ID em seu espaço reservado você pode injetar conteúdo dentro deste modelo desde modelos filhos usando esta ID como referência dentro de um elemento <asp:content />]]>
+ Selecione uma id de espaço reservado da lista abaixo. Você pode escolher somente as IDs em seu modelo mestre atual.]]>
+ Clique para ver a imagem em seu tamanho original
+ Escolha item
+ Ver Item em Cache
+
+
+ %0%' abaixo Você pode adicionar mais linguagens sob 'linguagens' no menu à esquerda]]>
+ Nome da Cultura
+
+
+ Tipos de nós filhos permitidos
+ Criar
+ Remover guia
+ Descrição
+ Nova guia
+ Guia
+ Miniatura
+
+
+ Adicionar valor prévio
+ Tipo de Dados do Banco de Dados
+ GUID do Editor de Propriedades
+ Editor de Propriedades
+ Botões
+ Habilitar configurações avançadas para
+ Habilitar menu de contexto
+ Tamanho padrão máximo para imagens inseridas
+ Stylesheets relacionadas
+ Mostrar Rótulo
+ Largura e altura
+
+
+ Seus dados foram salvos mas antes que possa publicar esta página existem alguns erros que precisam ser concertados:
+ O provedor de membros (Membership provider) atual não suporta alterações de senha (EnablePasswordRetrieval tem que estar definica como true)
+ %0% já existe
+ Houve erros:
+ Houve erros:
+ A senha deve ter no mínimo %0% caracteres e conter pelo menos %1% caractere(s) não alfa-númérico
+ %0% tem que ser um inteiro
+ O campo %0% na guia %1% é mandatório
+ %0% é um campo mandatório
+ %0% em %1% não está no formato correto
+ %0% não está em um formato correto
+
+
+ NOTA! Mesmo que CodeMirror esteja habilitado pela configuração o mesmo foi desabilitado em Internet Explorer pois não é estável o suficiente.
+ Favor preencher ambos apelidos e nome na sua nova propriedade de tipo!
+ Houve um erro com o acesso de ler/escrever em um arquivo ou pasta específica
+ Favor digitar um título
+ Favor escolher um tipo
+ Você está prestes a tornar esta figura maior que o tamanho original. Tem certeza que deseja proceguir?
+ Erro no script python
+ O script pyton não foi salvo por que contém erro(s)
+ Nó inicial removido, favor entrar em contato com seu administrador
+ Favor marcar conteúdo antes de alterar o estilo
+ Nenhum estilo ativo disponível
+ Favor colocar o cursos à esquerda das duas células que deseja mesclar
+ Você não pode dividir uma célula que não foi mesclada.
+ Erro na fonta XSLT
+ O XSLT não foi salvo porque contém erro(s)
+
+
+ Sobre
+ Ação
+ Adicionar
+ Apelido
+ Tem certeza?
+ Borda
+ por
+ Cancelar
+ Margem da célula
+ Escolher
+ Fechar
+ Fechar Janela
+ Comentário
+ Confirmar
+ Restrições de proporções
+ Continuar
+ Copiar
+ Criar
+ Banco de Dados
+ Data
+ Padrão
+ Remover
+ Removido
+ Removendo...
+ Desenho
+ Dimensões
+ Abaixo
+ Download
+ Editar
+ Editado
+ Elementos
+ Email
+ Erro
+ Buscar
+ Altura
+ Ajuda
+ Ícone
+ Importar
+ Margem interna
+ Inserir
+ Instalar
+ Justificar
+ Idioma
+ Esboço
+ Carregando
+ Travado
+ Login
+ Sair
+ Logout
+ Macro
+ Mover
+ Nome
+ Novo
+ Próximo
+ Não
+ de
+ OK
+ Abrir
+ ou
+ Senha
+ Caminho
+ ID do Espaço Reservado
+ Um momento por favor...
+ Prévio
+ Propriedades
+ Email para receber dados do formulário
+ Lixeira
+ Remanescentes
+ Renomear
+ Renovar
+ Tentar novamente
+ Permissões
+ Busca
+ Servidor
+ Mostrar
+ Mostrar página durante envio
+ Tamanho
+ Classificar
+ Submit
+ Tipo
+ Digite para buscar...
+ Acima
+ Atualizar
+ Atualizar
+ Subir (Upload)
+ Url
+ Usuário
+ Usuário
+ Valor
+ Ver
+ Bem Vindo(a)...
+ Largura
+ Sim
+ Reorder
+ I am done reordering
+
+
+ Cor de fundo
+ Negrito
+ Cor do Texto
+ Fonte
+ Texto
+
+
+ Página
+
+
+ O instalador não pôde conectar-se ao banco de dados.
+ Não foi possível salvar o arquivo web.config. Favor modificar a linha de conexão manualmente.
+ Seu banco de dados foi encontrado e identificado como
+ Configuração do Banco de Dados
+ instalar para instalar o banco de dados do Umbraco %0%]]>
+ Próximo para prosseguir.]]>
+ Banco de dados não encontrado! Favor checar se a informação no "connection string" do "web.config" esteja correta.
+
Para prosseguir, favor editar o arquivo "web.config" (usando Visual Studio ou seu editor de texto favorito), role até embaixo, adicione a connection string para seu banco de dados com a chave de nome "UmbracoDbDSN" e salve o arquivo
]]>
+ Favor contatar seu provedor de internet ou hospedagem web se necessário. Se você estiver instalando em uma máquina ou servidor local é possível que você precise dessas informações por um administrador de sistema.]]>
+
+ Pressione o botão atualizar para atualizar seu banco de dados para Umbraco %0%
+
+ Não se preocupe - nenhum conteúdo será removido e tudo estará funcionando depois disto!
+
+ ]]>
+ Pressione Próximo para prosseguir.]]>
+ próximo para continuar com o assistente de configuração]]>
+ A senha do usuário padrão precisa ser alterada!]]>
+ O usuário padrão foi desabilitado ou não tem acesso à Umbraco!
Nenhuma ação posterior precisa ser tomada. Clique Próximo para prosseguir.]]>
+ A senha do usuário padrão foi alterada com sucesso desde a instalação!
Nenhuma ação posterior é necessária. Clique Próximo para prosseguir.]]>
+ Senha foi alterada!
+
+ Umbraco cria um usuário padrão com o login ('admin') e senha ('default'). É importante que a senha seja alterada para algo único.
+
+
+ Este passo irá checar a senha do usuário padrão e sugerir uma alteração se necessário.
+
+
+ ]]>
+ Comece com o pé direito, assista nossos vídeos introdutórios
+ Ao clicar no próximo botão (ou modificando o UmbracoConfigurationStatus no web.config), você aceita a licença deste software cmo especificado na caixa abaixo. Note que esta distribuição de Umbraco consiste em duas licenças diferentes, a licença aberta MIT para a framework e a licença de software livre (freeware) Umbraco que cobre o UI.
+ Nenhum instalado ainda.
+ Pastas e arquivos afetados
+ Mais informações em como configurar permissões para Umbraco aqui
+ Você precisa conceder permissão de modificação ASP.NET aos seguintes arquivos/pastas
+ Suas permissões estão quase perfeitas!
+Você pode correr Umbraco sem problemas, mas não vai ser capaz de instalar pacotes que são recomendados para tirar total vantagem de Umbraco.]]>
+ Como Resolver
+ Clique aqui para ler a versão texto
+ vídeo tutorial sobre configuração de permissões de pastas para Umbraco ou leia a versão texto.]]>
+ Suas permissões podem ser um problema!
+
+Você pode correr Umbraco sem problemas mas não será capaz de criar pastas ou instalar pacotes que são recomendados para tirar total vantagem de Umbraco.]]>
+ Suas permissões não estão prontas para Umbraco!
+
+Para correr Umbraco você vai precisar atualizar as configurações de permissões.]]>
+ Suas configurações de permissões estão perfeitas!
Você está pronto para correr o Umbraco e instalar pacotes!]]>
+ Resolvendo problemas de pastas
+ Siga este link para mais informações sobre problemas com ASP.NET e criação de pastas
+ Configurando permissões de pastas
+
+ Eu quero começar do zero
+ learn how)
+ Você ainda pode escolher instalar Runway mais tarde. Favor ir à seção Desenvolvedor e selecione pacotes.]]>
+ Você acabou de configurar uma plataforma Umbraco limpa. O que deseja fazer a seguir?
+ Runway está instalado
+
+Esta é nossa lista de módulos recomendados, selecione os que gostaria de instalar, ou veja a lista completa de módulos]]>
+ Somente recomendado para usuários experientes
+ Eu quero começar com um site simples
+
+ "Runway" é um website simples que provê alguns documentos básicos e modelos. O instalador pode configurar Runway automaticamente mas você pode editar facilmente, extender ou removê-lo. Não é necessário e você pode perfeitamente usar Umbraco sem ele.
+No entanto, Runway oferece uma fundação básica sobre melhores práticas em como começar o mais rápido possível.
+Se escolher instalar Runway você pode opcionalmente selecionar blocos de construção básicos chamados módulos Runway para melhorar suas páginas Runway.
+
+ Incluso com Runway: Página Inicial, Começando, Instalando Módulos.
+ Módulos Opcionais: Navegação de Topo, Mapa de Site, Contato, Galeria.
+
+
+ ]]>
+ O que é Runway
+ Passo 1/5 Aceitar Licença
+ Passo 2/5: Configuração do Banco de Dados
+ Passo 3/5: Validando Permissões de Arquivos
+ Passo 4/5: Checar segurança Umbraco
+ Passo 5/5: Umbraco está pronto para ser usado
+ Obrigado por escolher Umbraco
+ Navegue seu site
+Você instalou Runway, então por que não ver como é seu novo website.]]>
+ Ajuda adicional e informações
+Consiga ajuda de nossa comunidade ganhadora de prêmios, navegue a documentação e assista alguns vídeos grátis sobre como construir um site simples, como usar pacotes e um guia prático sobre a terminologia Umbraco]]>
+ Umbraco %0% está instalado e pronto para uso
+ web.config e atualizar a chave AppSettings UmbracoConfigurationStatus no final para '%0%'.]]>
+ iniciar instantâneamente clicando em "Lançar Umbraco" abaixo. Se você é novo com Umbraco você pode encontrar vários recursos em nossa página para iniciantes.]]>
+ Lançar Umbraco
+Para gerenciar seu website, simplesmente abra a área administrativa do Umbraco para começar adicionando conteúdo, atualizando modelos e stylesheets e adicionando nova funcionalidade]]>
+ Conexão ao banco falhou.
+ Umbraco Versão 3
+ Umbraco Versão 4
+ Assistir
+ Umbraco %0% para uma nova instalação ou atualizando desde verão 3.0.
+
+ ]]>
+ [%0%] Notificação sobre %1% realizada em %2%
+ Notificações
+
+
+ e localizando o pacote. Pacotes Umbraco tem extensão ".umb" ou ".zip".]]>
+ Autor
+ Demonstração
+ Documentação
+ Dado meta do pacote
+ Nome do pacote
+ Pacote não contém nenhum item
+
+Você pode remover com segurança do seu sistema clicando em "desinstalar pacote" abaixo.]]>
+ Nenhuma atualização disponível
+ Oções do pacote
+ Leia-me do pacote
+ Repositório do pacote
+ Confirmar desinstalação
+ Pacote foi desinstalado
+ O pacote foi desinstalado com sucesso
+ Desinstalar pacote
+
+Aviso: quaisquer documentos, mídia, etc dependentes dos itens que forem removidos vão parar de funcionar e podem levar à instabilidade do sistema. Então desinstale com cuidado. Se tiver dúvidas, contate o autor do pacote]]>
+ Baixar atualização pelo repositório
+ Atualizar pacote
+ Instruções de atualização
+ Há uma atualizaçào disponível para este pacote. Você pode baixá-lo diretamente do repositório de pacotes do Umbraco.
+ Versão do pacote
+ Ver website do pacote
+
+
+ Colar com formatação completa (Não recomendado)
+ O texto que você está tentando colar contém caracteres ou formatação especial. Isto pode ser causado ao copiar textos diretamente do Microsoft Word. Umbraco pode remover os caracteres ou formatação especial automaticamente para que o conteúdo colado seja mais adequado para a internet.
+ Colar como texto crú sem nenhuma formatação
+ Colar, mas remover formatação (Recomendado)
+
+
+ Proteção baseada em função
+ usando grupos de membros do Umbraco.]]>
+ autenticação baseada em função.]]>
+ Página de Erro
+ Usado quando as pessoas estão logadas, mas não para ter acesso
+ Escolha como restringir o acesso à esta página
+ %0% agora está protegido
+ Proteção removida de %0%
+ Página de Login
+ Escolha a página que tem o formulário de login
+ Remover Proteção
+ Selecione as páginas que contém o formulário de login e mensagens de erro
+ Escolha as funções que terão acesso à esta página
+ Defina o login e senha para esta página
+ Proteção à um usuário específico
+ Se você deseja configurar proteção simples usando somente um usuário e senha
+
+
+ %0% não pode ser publicado devido à uma extensão de terceiros que cancelou a ação.
+ Incluir páginas filhas ainda não publicadas
+ Publicação em progresso - favor aguardar...
+ %0% de %1% páginas foram publicadas...
+ %0% foi publicada
+ %0% e sub-páginas foram publicadas
+ Publicar %0% e todoas suas sub-páginas
+ ok para publicar %0% e assim fazer com que seu conteúdo se torne disponível.
+Você pode publicar esta página e todas suas sub-páginas ao selecionar publicar todos filhos abaixo.]]>
+
+
+ Adicionar link externo
+ Adicionar link interno
+ Adicionar
+ Legenda
+ Página interna
+ URL
+ Mover Abaixo
+ Mover Acima
+ Abrir em nova janela
+ Remover Link
+
+
+ Versão atual
+ Texto vermelho não será mostrado na versão selecionada; verde significa adicionado]]>
+ Documento foi revertido
+ Isto mostra a versão selecionada como html se você deseja ver as diferenças entre as 2 versões ao mesmo tempo use a visão em diff
+ Reverter à
+ Selecione versão
+ Ver
+
+
+ Editar arquivo de script
+
+
+ Porteiro
+ Conteúdo
+ Mensageiro
+ Desenvolvedor
+ Assistente de Configuração Umbraco
+ Mídia
+ Membros
+ Boletins Informativos
+ Configurações
+ Estatísticas
+ Tradução
+ Usuários
+
+
+ Modelo padrão
+ Chave do Dicionário
+ Para importar um tipo de documento encontre o arquivo ".udt" em seu computador clicando em "Navegar" e depois clicando em "Importar"(você pode confirmar na próxima tela)
+ Novo Título da Guia
+ Tipo de Nó
+ Tipo
+ Stylesheet
+ Propriedade de Stylesheet
+ Guia
+ Título da Guia
+ Guias
+
+
+ Sort order
+ Creation date
+ Classificação concluída.
+ Arraste os diferentes itens para cima ou para baixo para definir como os mesmos serão arranjados. Ou clique no título da coluna para classificar a coleção completa de itens
+ Não feche esta janela durante a classificação]]>
+
+
+ Publicação foi cancelada por add-in de terceiros
+ Tipo de propriedade já existe
+ Tipo de propriedade criada
+ Tipo de Dado: %1%]]>
+ Tipo de propriedade removido
+ Tipo de Documento salvo
+ Guia criada
+ Guia removida
+ Guia com ID: %0% removida
+ Stylesheet não salva
+ Stylesheet salva
+ Stylesheet salva sem nenhum erro
+ Typo de Dado salvo
+ Item de Dicionário salvo
+ Publicação falhou porque a página pai não está publicada
+ Conteúdo publicado
+ e visível no website
+ Conteúdo salvo
+ Lembre-se de publicar para tornar as mudanças visíveis
+ Enviado para Aprovação
+ Alterações foram enviadas para aprovação
+ Membro salvo
+ Propriedade de Stylesheet salva
+ Stylesheet salva
+ Modelo salvo
+ Erro ao salvar usuário (verificar log)
+ Usuário Salvo
+ Arquivo não salvo
+ Arquivo não pode ser salvo. Favor checar as permissões do arquivo
+ Arquivo salvo
+ Arquivo salvo sem nenhum erro
+ Linguagem salva
+ Script Python não salvo
+ Script python não pode ser salvo devido à erro
+ Script Python salvo
+ Nenhum erro no script python
+ Modelo não salvo
+ Favor confirmar que não existem 2 modelos com o mesmo apelido
+ Modelo salvo
+ Modelo salvo sem nenhum erro!
+ XSLT não salvo
+ XSLT continha um erro
+ XSLT não pode ser salvo, cheque as permissões do arquivo
+ XSLT salvo
+ Nenhum erro no XSLT
+
+
+ Use sintaxe CSS ex: h1, .redHeader, .blueTex
+ Editar stylesheet
+ Editar propriedade do stylesheet
+ Nome para identificar a propriedade de estilo no editor de texto rico (richtext)
+ Prévia
+ Estilos
+
+
+ Editar modelo
+ Inserir área de conteúdo
+ Inserir área de conteúdo em espaço reservado
+ Inserir item de dicionário
+ Inserir Macro
+ Inserir campo de página Umbraco
+ Modelo mestre
+ Guia rápido para etiquetas de modelos Umbraco
+ Modelo
+
+
+ Choose type of content
+ Choose a layout
+ Add a row
+ Add content
+ Drop content
+ Settings applied
+
+ This content is not allowed here
+ This content is allowed here
+
+ Click to embed
+ Click to insert image
+ Image caption...
+ Write here...
+
+ Grid Layouts
+ Layouts are the overall work area for the grid editor, usually you only need one or two different layouts
+ Add Grid Layout
+ Adjust the layout by setting column widths and adding additional sections
+ Row configurations
+ Rows are predefined cells arranged horizontally
+ Add row configuration
+ Adjust the row by setting cell widths and adding additional cells
+
+ Columns
+ Total combined number of columns in the grid layout
+
+ Settings
+ Configure what settings editors can change
+
+ Styles
+ Configure what styling editors can change
+
+ Settings will only save if the entered json configuration is valid
+
+ Allow all editors
+ Allow all row configurations
+
+
+ Campo alternativo
+ Texto alternativo
+ Letra Maíscula ou minúscula
+ Escolha campo
+ Converter Quebra de Linhas
+ Substitui quebra de linhas com a etiqueta html <br>
+ Sim, Data somente
+ Formatar como data
+ Codificar HTML
+ Vai substituir caracteres especiais por seus equivalentes em HTML.
+ Será inserida após o valor do campo
+ Será inserida antes do valor do campo
+ Minúscula
+ Nenhum
+ Inserir após campo
+ Inserir antes do campo
+ Recursivo
+ Remover etiquetas de parágrafo
+ Removerá quaisquer <P> do começo ao fim do texto
+ Maiúscula
+ Codificar URL
+ Vai formatar caracteres especiais em URLs
+ Será usado somente quando os valores nos campos acima estiverem vazios
+ Este campo somente será usado se o campo primário estiver em vazio
+ Sim, com hora. Separador:
+
+
+ Tarefas designadas à você
+ designadas à você. Para ver os detalhes, incluinddo comentários, clique em "Detalhes" ou no nome da página.
+Você também pode baixar a página como XML ao clicar no link "Download XML".
+Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fechar".]]>
+ fechar tarefa
+ Detalhes da Tradução
+ Download todas as tarefas de tradução como xml
+ Download Xml
+ Download Xml DTD
+ Campos
+ Incluir sub-páginas
+
+ Tarefa de tradução [%0%] para %1%
+ Nenhum usuário tradutor encontrado. Favor criar um usuário tradutor antes que possa começar a enviar conteúdo para tradução
+ Tarefas criadas por você
+ criadas por você. Para ver os detalhes, incluindo comentários, clique em "Detalhes" ou no nome da página. Você também pode baixar a página em XML ao clicar no link "Download XML".
+Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fechar".]]>
+ A página '%0%' foi enviada para tradução
+ Enviar página '%0%' para tradução
+ Designada por
+ Tarefa aberta
+ Total de palavras
+ Traduzir para
+ Tradução concluída.
+ Você pode visualizar as páginas que acaba de traduzir ao clicar abaixo. Se a página original for encontrada você poderá fazer a comparação entre as 2 páginas.
+ Tradução falhou, o arquivo xml pode estar corrupto
+ Opções de Tradução
+ Tradutor
+ Upload Xml de Tradução
+
+
+ Navegador de Cache
+ Lixeira
+ Pacotes criados
+ Tipo de Dado
+ Dicionário
+ Pacotes instalados
+ Instalar tema
+ Instalar kit de iniciante
+ Linguagens
+ Instalar pacote local
+ Macros
+ Tipos de Mídia
+ Membros
+ Grupos de Membros
+ Funções
+ Tipo de Membro
+ Tipos de Documentos
+ Pacotes
+ Pacotes
+ Arquivos Python
+ Instalar desde o repositório
+ Instalar Runway
+ Módulos Runway
+ Arquivos de Script
+ Scripts
+ Stylesheets
+ Modelos
+ Arquivos XSLT
+
+
+ Nova atualização pronta
+ %0% está pronto, clique aqui para download
+ Nenhuma conexão ao servidor
+ Erro ao procurar por atualização. Favor revisar os detalhes (stack-trace) para mais informações
+
+
+ Administrador
+ Campo de Categoria
+ Alterar Sua Senha
+ você pode alterar sua senha para acessar a área administrativa do Umbraco preenchendo o formulário abaixo e clicando no botão 'Alterar Senha'
+ Canal de Conteúdo
+ Campo de descrição
+ Desabilitar Usuário
+ Tipo de Documento
+ Editor
+ Campo de excerto
+ Linguagem
+ Login
+ Nó Inicial na Biblioteca de Mídia
+ Seções
+ Desabilitar Acesso Umbraco
+ Senha
+ Sua senha foi alterada!
+ Favor confirmar sua nova senha
+ Digite sua nova senha
+ Sua nova senha não pode estar em branco!
+ Há uma diferença entre a nova senha e a confirmação da senha. Favor tentar novamente!
+ A confirmação da senha não é igual à nova senha!
+ Substituir permissões do nó filho
+ Vocês está modificando permissões para as páginas no momento:
+ Selecione páginas para modificar suas permissões
+ Buscar todos filhos
+ Nó Inicial do Conteúdo
+ Nome de Usuário
+ Permissões de usuário
+ Tipo de usuário
+ Tipos de usuários
+ Escrevente
+
+
diff --git a/WebCms/Umbraco/Config/Lang/ru.xml b/WebCms/Umbraco/Config/Lang/ru.xml
new file mode 100644
index 0000000..8995115
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/ru.xml
@@ -0,0 +1,1415 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Языки и домены
+ История исправлений
+ Просмотреть
+ Изменить тип документа
+ Копировать
+ Создать
+ Создать пакет
+ Значение по умолчанию
+ Удалить
+ Отключить
+ Очистить корзину
+ Экспортировать
+ Импортировать
+ Импортировать пакет
+ Править на месте
+ Выйти
+ Переместить
+ Уведомления
+ Публичный доступ
+ Опубликовать
+ Обновить узлы
+ Опубликовать весь сайт
+ Восстановить
+ Разрешения
+ Откатить
+ Направить на публикацию
+ Направить на перевод
+ Сортировать
+ Направить на публикацию
+ Перевести
+ Скрыть
+ Обновить
+
+
+ Добавить новый домен
+ Домен
+ Создан новый домен '%0%'
+ Домен '%0%' удален
+ Домен с именем '%0%' уже существует
+ Также здесь допустимы части адресов URL первого уровня, например "example.com/en",
+ однако их следует по возможности избегать. Рекомендуется использовать настройку культуры (языка), расположенную выше.]]>
+ Домен '%0%' обновлен
+ Такой домен уже назначен.
+ Унаследовать
+ Недопустимый формат домена.
+ Недопустимый узел.
+ Язык
+ Править существующие домены
+ Недостаточно полномочий.
+ удалить
+ Домены
+ Язык (культура)
+ или унаследуйте язык от родительских узлов.
+ Эта установка будет применена также и к текущему узлу, если только для него ниже явно не задан домен.]]>
+
+
+ Наблюдать за
+
+
+ Завершено
+
+ Удален %0% элемент
+ Удалено %0% элементов
+ Удален %0% из %1% элементов
+ Удалено %0% из %1% элементов
+
+ Опубликован %0% элемент
+ Опубликовано %0% элементов
+ Опубликован %0% из %1% элементов
+ Опубликовано %0% из %1% элементов
+
+ Скрыт %0% элемент
+ Скрыто %0% элементов
+ Скрыт %0% из %1% элементов
+ Скрыто %0% из %1% элементов
+
+ Перенесен %0% элемент
+ Перенесено %0% элементов
+ Перенесен %0% из %1% элементов
+ Перенесено %0% из %1% элементов
+
+ Скопирован %0% элемент
+ Скопировано %0% элементов
+ Скопирован %0% из %1% элементов
+ Скопировано %0% из %1% элементов
+
+
+ Полужирный
+ Снять выбор
+ Уменьшить отступ
+ Вставить поле формы
+ Сгенерировать модели
+ Вставить графический заголовок
+ Править исходный код HTML
+ Увеличить отступ
+ Курсив
+ По центру
+ По левому краю
+ По правому краю
+ Вставить ссылку
+ Вставить якорь (локальную ссылку)
+ Маркированный список
+ Нумерованный список
+ Вставить макрос
+ Вставить изображение
+ Править связи
+ Вернуться к списку
+ Сохранить
+ Сохранить и построить модели
+ Сохранить и опубликовать
+ Сохранить и направить на публикацию
+ Сохранить список
+ Выбрать
+ Выбрать текущую папку
+ выбранные
+ Предварительный просмотр
+ Предварительный просмотр запрещен, так как документу не сопоставлен шаблон
+ Другие действия
+ Выбрать стиль
+ Показать стили
+ Вставить таблицу
+
+
+ Чтобы сменить тип документа для выбранного узла, сначала выберите тип из списка разрешенных для данного расположения.
+ Затем подтвердите и/или исправьте сопоставление свойств текущего типа документа свойствам нового и нажмите "Сохранить".
+ Документ переопубликован.
+ Текущее свойство
+ Текущий тип
+ Тип документа не может быть изменен, так как для данного расположения нет разрешенных альтернатив.
+ Альтернативный тип станет доступным, если его разрешить как тип, пригодный для создания дочерних узлов внутри родительского узла данного документа.
+ Тип документа изменен
+ Сопоставление свойств
+ Сопоставлено свойству
+ Новый шаблон
+ Новый тип
+ нет
+ Узел
+ Выберите новый тип документа
+ Тип документа выбранного узла успешно изменен на [new type] и следующие свойства были перенесены:
+ в
+ Невозможно закончить перенос свойств, так как одно или более свойства имеют больше чем одно сопоставление.
+ Показаны только допустимые для данного расположения альтернативные типы.
+
+
+ Вы не указали ни одного допустимого цвета
+
+
+ Черный
+ Зеленый
+ Желтый
+ Оранжевый
+ Синий
+ Красный
+
+
+ Об этой странице
+ Алиас
+ (как бы Вы описали изображение по телефону)
+ Альтернативные ссылки
+ Альтернативный текст (необязательно)
+ Элементы списка
+ Нажмите для правки этого элемента
+ Создано пользователем
+ Исходный автор
+ Дата создания
+ Дата/время создания документа
+ Тип документа
+ Редактирование
+ Скрыть
+ ВНИМАНИЕ: невозможно получить URL документа (внутренняя ошибка - подробности в системном журнале)
+ Опубликовано
+ Этот документ был изменен после публикации
+ Этот документ не опубликован
+ Документ опубликован
+ Здесь еще нет элементов.
+ В этом списке пока нет элементов.
+ Ссылка на медиа-элементы
+ Тип медиа-контента
+ Группа участников
+ Член групп(ы)
+ Роль участника
+ Тип участника
+ Дата не указана
+ Заголовок страницы
+ Не является членом групп(ы)
+ Свойства
+ Этот документ опубликован, но скрыт, потому что его родительский документ '%0%' не опубликован
+ ВНИМАНИЕ: этот документ опубликован, но его нет в глобальном кэше (внутренняя ошибка - подробности в системном журнале)
+ Опубликовать
+ Состояние публикации
+ Опубликовать
+ Очистить дату
+ ВНИМАНИЕ: этот документ опубликован, но его URL вступает в противоречие с документом %0%
+ Это время будет соответствовать следующему времени на сервере:
+ Что это означает?]]>
+ Порядок сортировки обновлен
+ Для сортировки узлов просто перетаскивайте узлы или нажмите на заголовке столбца. Вы можете выбрать несколько узлов, удерживая клавиши "shift" или "ctrl" при пометке
+ Статистика
+ Цель
+ Заголовок (необязательно)
+ Тип
+ Скрыть
+ Распубликовать
+ Последняя правка
+ Дата/время редактирования документа
+ Обновлено
+ Удалить файл
+ Ссылка на документ
+
+
+ Композиции
+ Вы не добавили ни одной вкладки
+ Добавить вкладку
+ Добавить новую вкладку
+ Унаследован от
+ Добавить свойство
+ Обязательная метка
+
+ Представление в формате списка
+ Устанавливает представление документа данного типа в виде сортируемого списка дочерних документов с функцией поиска, в отличие от обычного представления дочерних документов в виде дерева
+
+ Допустимые шаблоны
+ Выберите перечень допустимых шаблонов для сопоставления документам данного типа
+ Разрешить в качестве корневого
+ Позволяет создавать документы этого типа в самом корне дерева содержимого
+ ДА - разрешить создание в качестве корневого
+
+ Допустимые типы дочерних документов
+ Позволяет указать перечень типов документов, допустимых для создания документов, дочерних к данному типу
+
+ Выбрать дочерний узел
+ Унаследовать вкладки и свойства из уже существующего типа документов. Вкладки будут либо добавлены в создаваемый тип, либо в случае совпадения названий вкладок будут добавлены наследуемые свойства.
+ Этот тип документов уже участвует в композиции другого типа, поэтому сам не может быть композицией.
+ В настоящее время нет типов документов, допустимых для построения композиции.
+
+ Доступные редакторы
+ Переиспользовать
+ Установки редактора
+
+ Конфигурирование
+
+ ДА, удалить
+
+ перемещены внутрь
+ скопированы внутрь
+ Выбрать папку для перемещения
+ Выбрать папку для копирования
+ в структуре дерева
+
+ Все типы документов
+ Все документы
+ Все медиа-элементы
+
+ , использующие этот тип документов, будут безвозвратно удалены, пожалуйста, подтвердите это действие.
+ , использующие этот тип медиа, будут безвозвратно удалены, пожалуйста, подтвердите это действие.
+ , использующие этот тип участников, будут безвозвратно удалены, пожалуйста, подтвердите это действие.
+
+ и все документы, использующие данный тип
+ и все медиа-элементы, использующие данный тип
+ и все участники, использующие данный тип
+
+ , использующие этот редактор, будут обновлены с применением этих установок
+
+ Участник может изменить
+ Показать в профиле участника
+ для вкладки не указан порядок сортировки
+
+
+ Где вы хотите создать новый %0%
+ Создать в узле
+ Новая папка
+ Новый тип данных
+ "Типы документов".]]>
+ "Типы медиа-материалов".]]>
+ Выберите тип и заголовок
+ Тип документа без сопоставленного шаблона
+
+
+ Обзор сайта
+ - Скрыть -
+ Если административная панель не загружается, Вам, возможно, следует разрешить всплывающие окна для данного сайта
+ было открыто в новом окне
+ Перезапустить
+ Посетить
+ Рады приветствовать
+
+
+ Название
+ Управление доменами
+ Закрыть это окно
+ Вы уверены, что хотите удалить
+ Вы уверены, что хотите запретить
+ Пожалуйста, подтвердите удаление из корзины %0% элементов
+ Вы уверены?
+ Вы уверены?
+ Вырезать
+ Править статью словаря
+ Изменить язык
+ Вставить локальную ссылку (якорь)
+ Вставить символ
+ Вставить графический заголовок
+ Вставить изображение
+ Вставить ссылку
+ Вставить макрос
+ Вставить таблицу
+ Последняя правка
+ Ссылка
+ Внутренняя ссылка:
+ Для того чтобы определить локальную ссылку, используйте "#" первым символом
+ Открыть в новом окне?
+ Свойства макроса
+ Этот макрос не имеет редактируемых свойств
+ Вставить
+ Изменить разрешения для
+ Все элементы в корзине сейчас удаляются. Пожалуйста, не закрывайте это окно до окончания процесса удаления
+ Корзина пуста
+ Вы больше не сможете восстановить элементы, удаленные из корзины
+ regexlib.com испытывает в настоящее время некоторые трудности, не зависящие от нас. Просим извинить за причиненные неудобства.]]>
+ Используйте поиск регулярных выражений для добавления сервиса проверки к полю Вашей формы. Например: 'email, 'zip-code', 'url'
+ Удалить макрос
+ Обязательное поле
+ Сайт переиндексирован
+ Кэш сайта был обновлен. Все опубликованное содержимое приведено в актуальное состояние, в то время как неопубликованное содержимое по-прежнему не опубликовано
+ Кэш сайта будет полностью обновлен. Все опубликованное содержимое будет обновлено, в то время как неопубликованное содержимое по-прежнему останется неопубликованным
+ Количество столбцов
+ Количество строк
+ Идентификация контейнера (placeholder)
+ путем назначения Вашему элементу-контейнеру уникального идентификатора (ID) позволит Вам автоматически включать в шаблон содержимое из дочерних шаблонов путем указания этого идентификатора (ID) в конструкции <asp:content />.]]>
+ Выберите идентификатор элемента-контейнера (placeholder)
+ из нижеследующего списка. Список ограничивается идентификаторами, определенными в родительском шаблоне данного шаблона.]]>
+ Кликните на изображении, чтобы увидеть полноразмерную версию
+ Выберите элемент
+ Просмотр элемента кэша
+ Создать папку...
+
+ Связать с оригиналом
+ Самое дружелюбное сообщество
+
+ Ссылка на страницу
+
+ Открывает документ по ссылке в новом окне или вкладке браузера
+ Открывает документ по ссылке в полноэкранном режиме
+ Открывает документ по ссылке в родительском фрейме
+
+ Ссылка на медиа-файл
+
+ Выбрать медиа
+ Выбрать значок
+ Выбрать элемент
+ Выбрать ссылку
+ Выбрать макрос
+ Выбрать содержимое
+ Выбрать участника
+ Выбрать группу участников
+
+ Это макрос без параметров
+
+ Провайдеры аутентификации
+ Подробное сообщение об ошибке
+ Трассировка стека
+ Внутренняя ошибка
+
+ Связать
+ Разорвать связь
+
+ учетную запись
+
+ Выбрать редактор
+
+
+ %0%' Добавить другие языки можно, воспользовавшись пунктом 'Языки' в меню слева
+ ]]>
+ Название языка (культуры)
+
+
+ Допустим как корневой
+ Только узлы таких типов (с установленным флагом) могут быть созданы в корневом уровне дерева содержимого или медиа-библиотеки
+ Допустимые типы дочерних узлов
+ Составные типы документов
+ Создать
+ Создать пользовательский список
+ Текущий список
+ Текущий тип данных в виде списка
+ Удалить вкладку
+ Описание
+ Включить представление в виде списка
+ Включает представление узлов, дочерних к узлу данного типа, в виде сортируемого списка с функцией фильтра и поиска. Такие дочерние узлы при этом перестают быть видимыми в дереве.
+ Новая вкладка
+ Удалить пользовательский список
+ Вкладка
+ Миниатюра
+
+
+ Добавить предустановленное значение
+ Тип данных в БД
+ GUID типа данных
+ Редактор свойства
+ Кнопки
+ Включить расширенные настройки для
+ Включить контекстное меню
+ Максимальный размер по-умолчанию для вставляемых изображений
+ Сопоставленные стили CSS
+ Показать метку
+ Ширина и высота
+
+
+ Ваши данные сохранены, но для того, чтобы опубликовать этот документ, Вы должны сначала исправить следующие ошибки:
+ Текущий провайдер ролей пользователей не поддерживает изменение пароля (необходимо свойству EnablePasswordRetrieval в файле web.config присвоить значение true)
+ %0% уже существует
+ Обнаружены следующие ошибки:
+ Обнаружены следующие ошибки:
+ Пароль должен состоять как минимум из %0% символов, хотя бы %1% из которых не являются буквами
+ %0% должно быть целочисленным значением
+ %0% в %1% является обязательным полем
+ %0% является обязательным полем
+ %0% в %1%: данные в некорректном формате
+ %0% - данные в некорректном формате
+
+
+ Получено сообщение об ошибке от сервера
+ ПРЕДУПРЕЖДЕНИЕ! Несмотря на то, что CodeMirror по-умолчанию разрешен в данной конфигурации, он по-прежнему отключен для браузеров Internet Explorer ввиду нестабильной работы
+ Укажите, пожалуйста, алиас и имя для этого свойства!
+ Использование данного типа файлов на сайте запрещено администратором
+ Ошибка доступа к указанному файлу или папке
+ Ошибка загрузки кода в частичном представлении (файл: %0%)
+ Ошибка загрузки пользовательского элемента управления '%0%'
+ Ошибка загрузки внешнего типа (сборка: %0%, тип: '%1%')
+ Ошибка загрузки макроса (файл: %0%)
+ "Ошибка разбора кода XSLT в файле: %0%
+ "Ошибка чтения XSLT-файла: %0%
+ Ошибка в конфигурации типа данных, используемого для свойства, проверьте тип данных
+ Укажите заголовок
+ Выберите тип
+ Вы пытаетесь увеличить изображение по сравнению с его исходным размером. Уверены, что хотите сделать это?
+ Ошибка в скрипте Python
+ Скрипт на языке Python не был сохранен, так как он содержит одну или несколько ошибок.
+ Начальный узел был удален, свяжитесь с Вашим администратором
+ Для смены стиля отметьте фрагмент текста
+ Не определен ни один доступный стиль
+ Поместите курсор в левую из двух ячеек, которые хотите объединить
+ Нельзя разделить ячейку, которая не была до этого объединена
+ Ошибка в XSLT-документе
+ XSLT-документ не был сохранен, так как он содержит одну или несколько ошибок
+
+
+ О системе
+ Действие
+ Действия
+ Добавить
+ Алиас
+ Все
+ Вы уверены?
+ Назад
+ Границы
+ пользователем
+ Отмена
+ Отступ ячейки
+ Выбрать
+ Закрыть
+ Закрыть окно
+ Примечание
+ Подтвердить
+ Сохранять пропорции
+ Далее
+ Копировать
+ Создать
+ База данных
+ Дата
+ По-умолчанию
+ Удалить
+ Удалено
+ Удаляется...
+ Дизайн
+ Размеры
+ Вниз
+ Скачать
+ Изменить
+ Изменено
+ Элементы
+ Email адрес
+ Ошибка
+ Найти
+ Папка
+ Высота
+ Справка
+ Иконка
+ Импорт
+ Внутренний отступ
+ Вставить
+ Установить
+ Неверно
+ Выравнивание
+ Название
+ Язык
+ Макет
+ Загрузка
+ БЛОКИРОВКА
+ Логин
+ Выйти
+ Выход
+ Макрос
+ Обязательно
+ Больше
+ Переместить
+ Название
+ Новый
+ Следующий
+ Нет
+ из
+ Ok
+ Открыть
+ или
+ Пароль
+ Путь
+ Идентификатор контейнера
+ Минуточку...
+ Предыдущий
+ Свойства
+ Email адрес для получения данных
+ Корзина
+ Ваша корзина пуста
+ Осталось
+ Переименовать
+ Обновить
+ Обязательное
+ Повторить
+ Разрешения
+ Поиск
+ К сожалению, ничего подходящего не нашлось
+ Результаты поиска
+ Сервер
+ Показать
+ Показать страницу при отправке
+ Размер
+ Сортировать
+ Отправить
+ Тип
+ Что искать?
+ Вверх
+ Обновить
+ Обновление
+ Загрузить
+ URL
+ Пользователь
+ Имя пользователя
+ Значение
+ Просмотр
+ Добро пожаловать...
+ Ширина
+ Да
+ Пересортировать
+ Пересортировка завершена
+ Предпросмотр
+ Сменить пароль
+ к
+ Список
+ Сохранение...
+ текущий
+ выбрано
+ Внедрить
+
+
+ Цвет фона
+ Полужирный
+ Цвет текста
+ Шрифт
+ Текст
+
+
+ Добавить содержимое
+ Сбросить содержимое
+ Добавить шаблон сетки
+ Настройте шаблон, задавая ширину колонок или добавляя дополнительные секции
+ Добавить конфигурацию строки
+ Настройте строку, задавая ширину ячеек или добавляя дополнительные ячейки
+ Добавить новые строки
+ Доступны все редакторы
+ Доступны все конфигурации строк
+ Выберите шаблон
+ Кликните для встраивания
+ Кликните для вставки изображения
+ Колонки
+ Недопустимый тип содержимого
+ Данный тип содержимого разрешен
+ Суммарное число колонок в шаблоне сетки
+ Шаблоны сетки
+ Шаблоны являются рабочим пространством для редактора сетки, обычно Вам понадобится не более одного или двух шаблонов
+ Вставить элемент
+ Заголовок для изображения...
+ Напишите...
+ Конфигурации строк
+ Строки - это последовательности ячеек с горизонтальным расположением
+ Установки будут сохранены только если они указаны в корректном формате json
+ Установки
+ Установки применены
+ Задайте установки, доступные редакторам для изменения
+ Стили
+ Задайте стили, доступные редакторам для изменения
+ Установить по-умолчанию
+ Выбрать дополнительно
+ Выбрать по-умолчанию
+ добавлены
+
+
+ Страница
+
+
+
+ Для параметра установлено рекомендованное значение: '%0%'.
+ Значение установлено в '%1%' для пути XPath '%2%' в файле конфигурации '%3%'.
+ Ожидаемое значение '%1%' для параметра '%2%' в файле конфигурации '%3%', найденное значение: '%0%'.
+ Найдено неожиданное значение '%0%' для параметра '%2%' в файле конфигурации '%3%'.
+
+
+ Параметр 'CustomErrors' установлен в '%0%'.
+ Параметр 'CustomErrors' сейчас установлен в '%0%'. Рекомендуется установить в '%1%' перед размещением сайта в сети.
+ Параметр 'CustomErrors' успешно установлен в '%0%'.
+
+ Параметр 'MacroErrors' установлен в '%0%'.
+ Параметр 'MacroErrors' установлен в '%0%', что может привести к неполной обработке части страниц или всех страниц сайта при наличии ошибок в макросах. Устранить это можно путем установки значения в '%1%'.
+ Параметр 'MacroErrors' теперь установлен в '%0%'.
+
+
+ Параметр 'Try Skip IIS Custom Errors' установлен в '%0%' и Вы используете IIS версии '%1%'.
+ Параметр 'Try Skip IIS Custom Errors' сейчас установлен в '%0%'. Рекомендуется установить в '%1%' для Вашего текущего IIS версии (%2%).
+ Параметр 'Try Skip IIS Custom Errors' успешно установлен в '%0%'.
+
+
+ Файл не существует: '%0%'.
+ '%0%' в файле конфигурации '%1%'.]]>
+ Обнаружена ошибка, для получения полной информации обратитесь к журналу: %0%.
+
+ Участники - всего в XML: %0%, всего: %1%, с ошибками: %2%
+ Медиа - всего в XML: %0%, всего: %1%Б с ошибками: %2%
+ Содержимое - всего в XML: %0%, всего опубликовано: %1%, с ошибками: %2%
+
+ Сертификат Вашего сайта отмечен как проверенный.
+ Ошибка проверки сертификата: '%0%'
+ Ошибка проверки адреса URL %0% - '%1%'
+ Сейчас Вы %0% просматриваете сайт, используя протокол HTTPS.
+ Параметр 'umbracoUseSSL' в секции 'appSetting' установлен в 'false' в файле web.config. Если Вам необходим доступ к сайту по протоколу HTTPS, нужно установить данный параметр в 'true'.
+ Параметр 'umbracoUseSSL' в секции 'appSetting' в файле установлен в '%0%', значения cookies %1% маркированы как безопасные.
+ Невозможно обновить значение параметра 'umbracoUseSSL' в файле web.config. Ошибка: %0%
+
+
+ Разрешить HTTPS
+ Устанавливает значение параметра 'umbracoSSL' в 'true' в секции 'appSettings' файла web.config.
+ Параметр 'umbracoUseSSL' в секции 'appSetting' файлf web.config теперь установлен в 'true', значения cookies будут промаркированы как безопасные.
+
+ Исправить
+ Невозможно исправление по результату проверки значения на 'ShouldNotEqual'.
+ Невозможно исправление по результату проверки значения на 'ShouldEqual' с предоставленным значением.
+ Значение для исправления не предоставлено.
+
+ Режим компиляции с отладкой выключен.
+ Режим компиляции с отладкой сейчас включен. Рекомендуется выключить перед размещением сайта в сети.
+ Режим компиляции с отладкой успешно выключен.
+
+ Режим трассировки выключен.
+ Режим трассировки сейчас включен. Рекомендуется выключить перед размещением сайта в сети.
+ Режим трассировки успешно выключен.
+
+ Все папки имеют корректно установленные параметры безопасности.
+
+ %0%.]]>
+ %0%. Если в них не разрешена запись, не нужно предпринимать никаких действий.]]>
+
+ Все файлы имеют корректно установленные параметры безопасности.
+
+ %0%.]]>
+ %0%. Если в них не разрешена запись, не нужно предпринимать никаких действий.]]>
+
+ X-Frame-Options, использующийся для управления возможностью помещать сайт в IFRAME на другом сайте.]]>
+ X-Frame-Options, использующийся для управления возможностью помещать сайт в IFRAME на другом сайте, не обнаружен.]]>
+ Установить заголовок в файле конфигурации
+ Добавляет значение в секцию 'httpProtocol/customHeaders' файла web.config, препятствующее возможному использованию этого сайта внутри IFRAME на другом сайте.
+ Значение, добавляющее заголовок, препятствующий использованию этого сайта внутри IFRAME другого сайта, успешно добавлено в файл web.config.
+ Невозможно обновить файл web.config. Ошибка: %0%
+
+
+ %0%.]]>
+ Заголовки, позволяющие выяснить базовую технологию сайта, не обнаружены.
+
+ В файле Web.config, не обнаружено параметров работы с отправкой электронной почты (секция 'system.net/mailsettings').
+ В файле Web.config в секции 'system.net/mailsettings' не обнаружены настройки почтового хоста.
+ Параметры отправки электронной почты (SMTP) настроены корректно, сервис работает как ожидается.
+ Сервер SMTP сконфигурирован на использование хоста '%0%' на порту '%1%', который в настоящее время недоступен. Пожалуйста, убедитесь, что настройки SMTP в файле Web.config в секции 'system.net/mailsettings' верны.
+
+ %0%.]]>
+ %0%.]]>
+
+
+ перейти к
+ Разделы справки для
+ Обучающие видео для
+ Лучшие обучающие видео-курсы по Umbraco
+
+
+ Сбросить
+
+
+ Программа установки не может установить подключение к базе данных.
+ Невозможно сохранить изменения в файл web.config. Пожалуйста, вручную измените настройки строки подключения к базе данных.
+ База данных обнаружена и идентифицирована как
+ Конфигурация базы данных
+ Установить чтобы установить базу данных Umbraco %0%
+ ]]>
+ Далее для продолжения.]]>
+ База данных не найдена! Пожалуйста, проверьте настройки строки подключения ("connection string") в файле конфигурации "web.config"
+
Для настройки откройте файл "web.config" с помощью любого текстового редактора и добавьте нужную информацию в строку подключения (параметр "UmbracoDbDSN"),
+ затем сохраните файл.
]]>
+
+ Пожалуйста, свяжитесь с Вашим хостинг-провайдером, если есть необходимость, а если устанавливаете на локальную рабочую станцию или сервер, то получите информацию у Вашего системного администратора.]]>
+ Нажмите кнопку Обновление
+ для того, чтобы привести Вашу базу данных
+ в соответствие с версией Umbraco %0%
+
Пожалуйста, не волнуйтесь, ни одной строки Вашей базы данных
+ не будет потеряно при данной операции, и после ее завершения все будет работать!
+ ]]>
+
+ Нажмите Далее для продолжения. ]]>
+
+ Далее для продолжения работы мастера настроек]]>
+ Пароль пользователя по-умолчанию необходимо сменить!]]>
+ Пользователь по-умолчанию заблокирован или не имеет доступа к Umbraco!
Не будет предпринято никаких дальнейших действий. Нажмите кнопку Далее для продолжения.]]>
+ Пароль пользователя по-умолчанию успешно изменен в процессе установки!
Нет надобности в каких-либо дальнейших действиях. Нажмите кнопку Далее для продолжения.]]>
+ Пароль изменен!
+ Umbraco создает пользователя по-умолчанию с именем входа ('admin') и паролем ('default').
+ ОЧЕНЬ ВАЖНО изменить пароль по-умолчанию на что-либо уникальное.]]>
+
+ Для начального обзора возможностей системы рекомендуем посмотреть ознакомительные видеоматериалы
+ Далее (или модифицируя вручную ключ "UmbracoConfigurationStatus" в файле "web.config"), Вы принимаете лицензионное соглашение для данного программного обеспечения, расположенное ниже. Пожалуйста, обратите внимание, что установочный пакет Umbraco отвечает двум различным типам лицензий: лицензии MIT на программные продукты с открытым исходным кодом в части ядра системы и свободной лицензии Umbraco в части пользовательского интерфейса.]]>
+ Система не установлена.
+ Затронутые файлы и папки
+ Более подробно об установке разрешений для Umbraco рассказано здесь
+ Вам следует установить разрешения для учетной записи ASP.NET на модификацию следующих файлов и папок
+ Установки разрешений в Вашей системе почти полностью отвечают требованиям Umbraco!
+
Вы имеете возможность запускать Umbraco без проблем, однако не сможете воспользоваться такой сильной стороной системы Umbraco как установка дополнительных пакетов расширений и дополнений.]]>
+ Как решить проблему
+ Нажмите здесь, чтобы прочесть текстовую версию документа
+ видео-материал, посвященный установке разрешений для файлов и папок в Umbraco или прочтите текстовую версию документа.]]>
+ Установки разрешений в Вашей файловой системе могут быть неверными!
+
Вы имеете возможность запускать Umbraco без проблем,
+ однако не сможете воспользоваться такой сильной стороной системы Umbraco как установка дополнительных пакетов расширений и дополнений.]]>
+ Установки разрешений в Вашей файловой системе не подходят для работы Umbraco!
+
Если Вы хотите продолжить работу с Umbraco,
+ Вам необходимо скорректировать установки разрешений.]]>
+ Установки разрешений в Вашей системе идеальны!
+
Вы имеете возможность работать с Umbraco в полном объеме включая установку дополнительных пакетов расширений и дополнений!]]>
+ Решение проблемы с папками
+ Воспользуйтесь этой ссылкой для получения более подробной информации о проблемах создания папок от имени учетной записи ASP.NET
+ Установка разрешений на папки
+
+
+ Здесь можно узнать об этом подробнее) Вы также можете отложить установку "Runway" на более позднее время. Перейдите к разделу "Для разработчиков" и выберите пункт "Пакеты".
+ ]]>
+ Вы только что установили чистую платформу Umbraco. Какой шаг будет следующим?
+ "Runway" установлен
+ Ниже приведен список модулей, рекомендованных к установке, измените его при необходимости, или ознакомьтесь с полным списком модулей
+ ]]>
+ Рекомендовано только для опытных пользователей
+ Я хочу начать с установки простого демонстрационного сайта
+ "Runway" - это простой демонстрационный веб-сайт, предоставляющий базовый перечень шаблонов и типов документов.
+ Программа установки может настроить "Runway" для Вас автоматически,
+ но Вы можете в дальнейшем свободно изменять, расширять или удалить его.
+ Этот демонстрационный сайт не является необходимой частью, и Вы можете свободно
+ использовать Umbraco без него. Однако, "Runway" предоставляет Вам возможность
+ максимально быстро познакомиться с базовыми принципами и техникой построения сайтов
+ на основе Umbraco. Если Вы выберете вариант с установкой "Runway",
+ Вам будет предложен выбор "базовых строительных блоков" (т.н. модулей Runway) для расширения возможностей страниц сайта "Runway".
+ В "Runway" входят:"Домашняя" (главная) страница, страница "Начало работы",
+ страница установки модулей. Дополнительные модули:Базовая навигация, Карта сайта, Форма обратной связи, Галерея.
+ ]]>
+ Что такое "Runway"
+ Шаг 1 из 5: Лицензионное соглашение
+ Шаг 2 из 5: конфигурация базы данных
+ Шаг 3 из 5: проверка файловых разрешений
+ Шаг 4 из 5: проверка безопасности
+ Шаг 5 из 5: система готова для начала работы
+ Спасибо, что выбрали Umbraco
+ Обзор Вашего нового сайтаВы установили "Runway",
+ почему бы не посмотреть, как выглядит Ваш новый сайт?]]>
+ Дальнейшее изучение и помощь
+ Получайте помощь от нашего замечательного сообщества пользователей, изучайте документацию или просматривайте наши свободно распространяемые видео-материалы о том, как создать собственный несложный сайт, как использовать расширения и пакеты, а также краткое руководство по терминологии Umbraco.]]>
+ Система Umbraco %0% установлена и готова к работе
+ web.config и изменить значение ключа UmbracoConfigurationStatus в секции AppSetting, установив его равным '%0%'.]]>
+ прямо сейчас,
+ воспользовавшись ссылкой "Начать работу с Umbraco". Если Вы новичок в мире Umbraco, Вы сможете найти много полезных ссылок на ресурсы на странице "Начало работы".]]>
+ Начните работу с Umbraco
+ Для того, чтобы начать администрировать свой сайт, просто откройте административную панель Umbraco и начните обновлять контент, изменять шаблоны страниц и стили CSS или добавлять новую функциональность]]>
+ Попытка соединения с базой данных потерпела неудачу.
+ Версия Umbraco 3
+ Версия Umbraco 4
+ Смотреть
+ Umbraco %0% в форме "чистой" установки или обновления предыдущей версии 3.x.
+
Перейдите по этой ссылке для того, чтобы сбросить Ваш пароль, или скопируйте текст ссылки и вставьте в адресную строку своего браузера:
%1%
]]>
+
+
+
+ Панель управления
+ Разделы
+ Содержимое
+
+
+ Нажмите, чтобы загрузить
+ Невозможна загрузка этого файла, этот тип файлов не разрешен для загрузки
+ Перетащите файлы сюда...
+ Ссылка на файл
+ или нажмите сюда, чтобы выбрать файлы
+ Разрешены только типы файлов:
+ Максимально допустимый размер файла:
+
+
+ Создать нового участника
+ Все участники
+
+
+ Построение моделей
+ это может занять некоторое время, пожалуйста, подождите
+ Модели построены
+ Модели не могут быть построены
+ Процесс построения моделей завершился ошибкой, подробности в системном журнале Umbraco
+
+
+ Выберите страницу...
+ Узел %0% был скопирован в %1%
+ Выберите, куда должен быть скопирован узел %0%
+ Узел %0% был перемещён в %1%
+ Выберите, куда должен быть перемещён узел %0%
+ был выбран как родительский узел для нового элемента, нажмите 'Ок'.
+ Не выбран узел! Пожалуйста выберите узел назначения, прежде чем нажать 'Ок'.
+ Текущий узел не может быть размещен непосредственно в корне дерева
+ Текущий узел не может быть размещён в выбранном Вами из-за несоответствия типов.
+ Текущий узел не может быть перемещен внутрь своих дочерних узлов
+ Данное действие не может быть осуществлено, так как Вы не имеете достаточных прав для совершения действий над одним или более дочерними документами.
+ Связать новые копии с оригиналами
+
+
+ Вы можете изменить уведомление для %0%
+
+ Здравствуйте, %0%
+
+
Это автоматически сгенерированное уведомление. Операция '%1%'
+ была произведена на странице '%2%'
+ пользователем '%3%'.
]]>
+ [%0%] Уведомление об операции %1% над документом %2%
+ Уведомления
+
+
+
+ и указав на нужный файл. Пакеты Umbraco обычно являются архивами с расширением ".umb" или ".zip".
+ ]]>
+ Автор
+ Демонстрация
+ Документация (описание)
+ Мета-данные пакета
+ Название пакета
+ Пакет ничего не содержит
+ Вы можете безопасно удалить данный пакет из системы, нажав на кнопку "Деинсталлировать пакет".]]>
+ Нет доступных обновлений
+ Опции пакета
+ Краткий обзор пакета
+ Репозиторий пакета
+ Подтверждение деинсталляции
+ Пакет деинсталлирован
+ Указанный пакет успешно удален из системы
+ Деинсталлировать пакет
+
+ Обратите внимание: все документы, медиа-файлы и другой контент, зависящий от этого пакета, перестанет нормально работать, что может привести к нестабильному поведению системы,
+ поэтому удаляйте пакеты очень осторожно. При наличии сомнений, свяжитесь с автором пакета.]]>
+ Скачать обновление из репозитория
+ Обновление пакета
+ Руководство по обновлению
+ Для данного пакета доступно обновление. Вы можете загрузить это обновление непосредственно из центрального репозитория пакетов Umbraco.
+ Версия пакета
+ История версий пакета
+ Перейти на веб-сайт пакета
+ Этот пакет уже установлен в системе
+ Этот пакет не может быть установлен, он требует наличия Umbraco версии как минимум %0%
+ Удаление...
+ Загрузка...
+ Импорт...
+ Установка...
+ Перезапуск, подождите, пожалуйста...
+ Все готово, сейчас браузер перезагрузит страницу, подождите, пожалуйста...
+
+
+ Вставить, полностью сохранив форматирование (не рекомендуется)
+ Текст, который Вы пытаетесь вставить, содержит специальные символы и/или элементы форматирования. Это возможно при вставке текста, скопированного из Microsoft Word. Система может удалить эти элементы автоматически, чтобы сделать вставляемый текст более пригодным для веб-публикации.
+ Вставить как простой текст без форматирования
+ Вставить с очисткой форматирования (рекомендуется)
+
+
+ Подтвердите пароль
+ Укажите Ваш email
+ Укажите описание...
+ Укажите имя...
+ Укажите теги (нажимайте Enter после каждого тега)...
+ Укажите фильтр...
+ Метка...
+ Назовите %0%...
+ Укажите пароль
+ Что искать...
+ Укажите имя пользователя
+
+
+ Остаться
+ Отменить изменения
+ Есть несохраненные изменения
+ Вы уверены, что хотите уйти с этой страницы? - на ней имеются несохраненные изменения
+
+
+ Расширенный: Защита на основе ролей (групп)
+ с использованием групп участников Umbraco.]]>
+ для применения ролевой модели безопасности.]]>
+ Страница сообщения об ошибке
+ Используется в случае, когда пользователь авторизован в системе, но не имеет доступа к документу.
+ Выберите способ ограничения доступа к документу
+ Правила доступа к документу %0% установлены
+ Правила доступа для документа %0% удалены
+ Страница авторизации (входа)
+ Используйте как страницу с формой для авторизации пользователей
+ Снять защиту
+ Выберите страницы авторизации и сообщений об ошибках
+ Выберите роли пользователей, имеющих доступ к документу
+ Установите имя пользователя и пароль для доступа к этому документу
+ Простой: Защита по имени пользователя и паролю
+ Применяйте, если хотите установить самый простой способ доступа к документу - явно указанные имя пользователя и пароль
+
+
+
+
+
+
+
+ Включая неопубликованные дочерние документы
+ Идет публикация. Пожалуйста, подождите...
+ %0% из %1% документов опубликованы...
+ Документ %0% опубликован.
+ Документ %0% и его дочерние документы были опубликованы
+ Опубликовать документ %0% и все его дочерние документы
+ Опубликовать для публикации документа %0%.
+ Тем самым Вы сделаете содержимое документа доступным для просмотра.
+ Вы можете опубликовать этот документ и все его дочерние документы, отметив опцию Опубликовать все дочерние документы.
+ Чтобы опубликовать ранее неопубликованные документы среди дочерних, отметьте опцию Включая неопубликованные дочерние документы.
+ ]]>
+
+
+ Остановить отслеживание URL
+ Запустить отслеживание URL
+ Первоначальный URL
+ Перенаправлен в
+ На данный момент нет ни одного перенаправления
+ Если опубликованный документ переименовывается или меняет свое расположение в дереве, а следовательно, меняется адрес (URL), автоматически создается перенаправление на новое местоположение этого документа.
+ Удалить
+ Вы уверены, что хотите удалить перенаправление с '%0%' на '%1%'?
+ Перенаправление удалено.
+ Ошибка удаления перенаправления.
+ Вы уверены, что хотите остановить отслеживание URL?
+ Отслеживание URL в настоящий момент остановлено.
+ Ошибка остановки отслеживания URL, более подробные сведения находятся в системном журнале.
+ Отслеживание URL в настоящий момент запущено.
+ Ошибка запуска отслеживания URL, более подробные сведения находятся в системном журнале.
+
+
+ Заголовок
+ Укажите заголовок
+ выбрать страницу сайта
+ указать внешнюю ссылку
+ Укажите ссылку
+ Ссылка
+ В новом окне
+
+
+ Текущая версия
+ Красным отмечен текст, которого уже нет в последней версии, зеленым - текст, который добавлен]]>
+ Произведен откат к ранней версии
+ Текущая версия показана в виде HTML. Для просмотра различий в версиях выберите режим сравнения
+ Откатить к версии
+ Выберите версию
+ Просмотр
+
+
+ Править файл скрипта
+
+
+ Аналитика
+ Смотритель
+ Содержимое
+ Курьер
+ Для Разработчиков
+ Формы
+ Помощь
+ Мастер конфигурирования Umbraco
+ Медиа-материалы
+ Участники
+ Рассылки
+ Установки
+ Статистика
+ Перевод
+ Пользователи
+ Добавить значок
+
+
+ в качестве родительского типа. Вкладки родительского типа не показаны и могут быть изменены непосредственно в родительском типе
+ Родительский тип контента разрешен
+ Данный тип контента использует
+ Создать шаблон для документов этого типа
+ Шаблон по-умолчанию
+ Словарная статья
+ Чтобы импортировать тип документа, найдите файл ".udt" на своем компьютере, нажав на кнопку "Обзор", затем нажмите "Импортировать" (на следующем экране будет запрошено подтверждение для этой операции).
+ Родительский тип документа
+ Заголовок новой вкладки
+ Тип узла (документа)
+ Для данной вкладки не определены свойства. Кликните по ссылке "Click here to add a new property" сверху, чтобы создать новое свойство.
+ Тип
+ Скрипт
+ Стили CSS
+ Правило стиля CSS
+ Вкладка
+ Заголовок вкладки
+ Вкладки
+
+
+ Добавить вкладку
+ Добавить свойство
+ Добавить редактор
+ Добавить шаблон
+ Добавить дочерний узел
+ Добавить дочерний
+
+ Изменить тип данных
+
+ Навигация по разделам
+
+ Ярлыки
+ показать ярлыки
+
+ В формате списка
+ Разрешить в качестве корневого
+
+
+ Порядок сортировки
+ Дата создания
+ Сортировка завершена
+ Перетаскивайте элементы на нужное место вверх или вниз для определения необходимого Вам порядка сортировки. Также можно щелкнуть по заголовкам столбцов, чтобы отсортировать все элементы сразу.
+ Не закрывайте это окно до окончания процесса сортировки.]]>
+
+
+ Процесс публикации был отменен установленным пакетом дополнений.
+ Такое свойство уже существует.
+ Свойство создано
+ Тип данных: %1%]]>
+ Свойство удалено
+ Тип документа сохранен
+ Вкладка создана
+ Вкладка удалена
+ Вкладка с идентификатором (id): %0% удалена
+ Документ скрыт (публикация отменена)
+ Стиль CSS не сохранен
+ Стиль CSS сохранен
+ Стиль CSS сохранен без ошибок
+ Тип данных сохранен
+ Статья в словаре сохранена
+ Публикация не завершена, так как родительский документ не опубликован
+ Документ опубликован
+ и является видимым
+ Документ сохранен
+ Не забудьте опубликовать, чтобы сделать видимым
+ Отослано на утверждение
+ Изменения отосланы на утверждение
+ Медиа-элемент сохранен
+
+ Участник сохранен
+ Правило стиля CSS сохранено
+ Стиль CSS сохранен
+ Шаблон сохранен
+ Произошла ошибка при сохранении пользователя (проверьте журналы ошибок)
+ Пользователь сохранен
+ Тип пользователей сохранен
+ Файл не сохранен
+ Файл не может быть сохранен. Пожалуйста, проверьте установки файловых разрешений
+ Файл сохранен
+ Файл сохранен без ошибок
+ Язык сохранен
+ Тип медиа сохранен
+ Тип участника сохранен
+ Представление не сохранено
+ Произошла ошибка при сохранении файла
+ Представление сохранено
+ Представление сохранено без ошибок
+ Cкрипт Python не сохранен
+ Cкрипт Python не может быть сохранен в связи с ошибками
+ Cкрипт Python сохранен
+ Cкрипт Python сохранен без ошибок
+ Шаблон не сохранен
+ Пожалуйста, проверьте, что нет двух шаблонов с одним и тем же алиасом (названием)
+ Шаблон сохранен
+ Шаблон сохранен без ошибок
+ XSLT-документ не сохранен
+ XSLT-документ содержит одну или несколько ошибок
+ XSLT-документ не может быть сохранен, проверьте установки файловых разрешений
+ XSLT-документ сохранен
+ Ошибок в XSLT-документе нет
+
+
+ Используется синтаксис селекторов CSS, например: h1, .redHeader, .blueTex
+ Изменить стиль CSS
+ Изменить правило стиля CSS
+ Название правила для отображения в редакторе документа
+ Предварительный просмотр
+ Стили
+
+
+ Править шаблон
+ Вставить контент-область
+ Вставить контейнер (placeholder)
+ Вставить статью словаря
+ Вставить макрос
+ Вставить поле документа
+ Мастер-шаблон
+ Краткая справка по тэгам шаблонов Umbraco
+ Шаблон
+
+
+ Альтернативное поле
+ Альтернативный текст
+ Регистр
+ Выбрать поле
+ Преобразовать переводы строк
+ Заменяет переводы строк на тэг html <br>
+ Пользовательские
+ Только дата
+ Кодировка
+ Форматировать как дату
+ Кодировка HTML
+ Заменяет спецсимволы эквивалентами в формате HTML
+ Будет добавлено после поля
+ Будет вставлено перед полем
+ В нижнем регистре
+ -Не указано-
+ Вставить после поля
+ Вставить перед полем
+ Рекурсивно
+ Удалить тэги параграфов
+ Удаляются тэги <p> в начале и в конце абзацев
+ Стандартные
+ В верхнем регистре
+ Кодирование URL
+ Форматирование специальных символов в URL
+ Это значение будет использовано только если предыдущие поля пусты
+ Это значение будет использовано только если первичное поле пусто
+ Дата и время. Разделитель:
+
+
+ Задачи, назначенные Вам
+ назначенные Вам. Для того, чтобы просмотреть подробные сведения,
+ включая комментарии, нажмите кнопку "Подробно" или просто кликните имя страницы. Вы также можете скачать XML-версию страницы нажав ссылку "Загрузить Xml". Чтобы закрыть задачу перевода, следует перейти к подробному просмотру и нажать там кнопку "Закрыть".
+ ]]>
+ Закрыть задачу
+ Подробности перевода
+ Загрузить все задачи по переводу в виде xml
+ Загрузить Xml
+ Загрузить xml DTD
+ Поля
+ Включить дочерние документы
+
+ [%0%] Задание по переводу %1%
+ Пожалуйста, выберите язык, на который нужно перевести документ(ы)
+ Пользователей-переводчиков не обнаружено. Пожалуйста, создайте пользователя с ролью переводчика, перед тем как отсылать содержимое на перевод
+ Созданные Вами задания
+ созданных Вами.
+ Чтобы увидеть подробные данные, включая комментарии, нажмите кнопку "Подробно" или просто кликните название страницы.
+ Вы также можете загрузить XML-версию страницы, нажав на ссылку "Загрузить Xml". Чтобы завершить задачу перевода, Вам следует перейти к подробному просмотру и нажать там кнопку "Закрыть".
+ ]]>
+ Документ '%0%' был отправлен на перевод
+ Отправить документ '%0%' на перевод
+ Задание назначил
+ Задача создана
+ Всего слов
+ Перевести на
+ Перевод завершен.
+ Вы можете просмотреть документы, переведенные Вами, кликнув на ссылке ниже. Если будет найден оригинал документа, Вы увидите его и переведенный вариант в режиме сравнения.
+ Перевод не сохранен, файл xml может быть поврежден
+ Опции перевода
+ Переводчик
+ Загрузить переведенный xml
+
+
+ Аналитика
+ Обзор кэша
+ Корзина
+ Созданные пакеты
+ Типы данных
+ Словарь
+ Установленные пакеты
+ Установить тему
+ Установить стартовый набор
+ Языки
+ Установить локальный пакет
+ Макросы
+ Типы медиа-материалов
+ Участники
+ Группы участников
+ Роли участников
+ Типы участников
+ Типы документов
+ Пакеты дополнений
+ Пакеты дополнений
+ Скрипты Python
+ Установить из репозитория
+ Установить Runway
+ Модули Runway
+ Файлы скриптов
+ Скрипты
+ Стили CSS
+ Шаблоны
+ Файлы XSLT
+
+
+ Доступны обновления
+ Обновление %0% готово, кликните для загрузки
+ Нет связи с сервером
+ Во время проверки обновлений произошла ошибка. Пожалуйста, просмотрите журнал трассировки для получения дополнительной информации.
+
+
+ Администратор
+ Поле категории
+ Изменить пароль
+ Вы можете сменить свой пароль для доступа к административной панели Umbraco, заполнив нижеследующие поля и нажав на кнопку 'Изменить пароль'
+ Подтверждение нового пароля
+ Канал содержимого
+ Поле описания
+ Отключить пользователя
+ Тип документа
+ Редактор
+ Исключить поле
+ Язык
+ Имя входа (логин)
+ Начальный узел в Медиа-библиотеке
+ Разделы
+ Новый пароль
+ Отключить доступ к административной панели Umbraco
+ Прежний пароль
+ Пароль
+ Ваш пароль доступа изменен!
+ Подтвердите новый пароль
+ Текущий пароль
+ Укажите новый пароль
+ Текущий пароль указан неверно
+ Пароль не может быть пустым
+ Новый пароль и его подтверждение не совпадают. Попробуйте еще раз
+ Новый пароль и его подтверждение не совпадают
+ Заменить разрешения для дочерних документов
+ Вы изменяете разрешения для следующих документов:
+ Выберите документы для изменения их разрешений
+ Сбросить пароль
+ Поиск всех дочерних документов
+ Сессия истекает через
+ Начальный узел содержимого
+ Переводчик
+ Имя пользователя
+ Разрешения для пользователя
+ Тип пользователя
+ Типы пользователей
+ Автор
+ Ваша недавняя история
+ Ваш профиль
+
+
+ Валидация
+ Валидация по формату email
+ Валидация числового значения
+ Валидация по формату Url
+ ...или указать свои правила валидации
+ Обязательно к заполнению
+
+
diff --git a/WebCms/Umbraco/Config/Lang/sv.xml b/WebCms/Umbraco/Config/Lang/sv.xml
new file mode 100644
index 0000000..7e3a11b
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/sv.xml
@@ -0,0 +1,939 @@
+
+
+
+ The Umbraco community
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ Hantera domännamn
+ Hantera versioner
+ Surfa på sidan
+ Ändra dokumenttyp
+ Kopiera
+ Skapa
+ Skapa paket
+ Standardvärde
+ Ta bort
+ Avaktivera
+ Töm papperskorgen
+ Exportera dokumenttyp
+ Importera dokumenttyp
+ Importera paket
+ Redigera i Canvas
+ Logga ut
+ Flytta
+ Meddelanden
+ Lösenordsskydd
+ Publicera
+ Ladda om noder
+ Publicera hela webbplatsen
+ Rättigheter
+ Ångra ändringar
+ Skicka för publicering
+ Skicka för översättning
+ Sortera
+ Skicka för publicering
+ Översätt
+ Avpublicera
+ Uppdatera
+
+
+ Lägg till nytt domännamn
+ Domännamn
+ Har skapat domännamnet %0%
+ Har tagit bort domännamnet %0%
+ Domänen %0% är redan tillagd
+ t.ex.: dittdomannamn.se, www.dittdomannamn.se
+ Domännamnet %0% har uppdaterats
+ Domänen är redan tilldelad
+ Ärv
+ Ogiltigt domännamn
+ Ogiltig nod
+ Språk
+ Redigera domännamn
+ Ingen behörighet
+ Ta bort
+ Domäner
+ Kultur
+ eller ärv kulturen från föregående noder. Appliceras även
+ på befintlig nod.]]>
+
+
+ Visar för
+
+
+ Fetstil
+ Minska indrag
+ Infoga formulärfält
+ Infoga grafisk rubrik
+ Ändra html
+ Öka indrag
+ Kursiv
+ Centrera
+ Vänsterjustera
+ Högerjustera
+ Infoga länk
+ Infoga intern länk (ankare)
+ Punktlista
+ Numrerad lista
+ Infoga macro
+ Infoga bild
+ Ändra relation
+ Återvänd till lista
+ Spara
+ Spara och publicera
+ Spara och skicka för godkännande
+ Välj
+ Välj aktuell mapp
+ Förhandsgranska
+ Förhandsgranskning är avstängt på grund av att det inte finns någon mall tilldelad
+ Ångra
+ Välj stil
+ Visa stil
+ Infoga tabell
+
+
+ För att ändra vald dokumenttyp, välj först i listan av giltiga typer för denna platsen.
+ Konfirmera sedan och/eller ändra mappningen av egenskaper från aktuell dokumenttyp till den nya, och klicka sedan på spara.
+ Innehållet har blivit publicerat på nytt
+ Aktuell egenskap
+ Aktuell typ
+ Dokumenttypen kan inte ändras, eftersom det inte finns några giltiga val för denna plats.
+ Dokumenttypen är ändrad
+ Mappningsegenskaper
+ Mappning till egenskap
+ Ny sidmall
+ Ny egenskap
+ ingen
+ Innehåll
+ Välj ny dokumenttyp
+ Dokumenttypen på valt innehåll har ändrats till [new type] utan problem och följande egenskaper är mappade:
+ till
+ Kunde inte slutföra egenskapsmappningen då en eller flera egenskaper har en eller flera mappningar definierade.
+ Enbart giltiga alternativa egenskaper visas för platsen
+
+
+ Du har inte konfigurerat några giltiga färger
+
+
+ Om denna sida
+ Alias
+ (hur skulle du beskriva denna bild för någon över telefon)
+ Alternativa länkar
+ Alternativ text (optionell)
+ Underliggande noder
+ Klicka för att redigera detta objekt
+ Skapad av
+ Ursprunglig författare
+ Skapad
+ Datum/tid som dokumentet skapades
+ Dokumenttyp
+ Redigering
+ Ta bort
+ Är publicerad
+ Detta objekt har ändrats efter publicering
+ Detta objekt är inte publicerat
+ Senast publicerat
+ Det finns inget att visa i listan
+ Länk till medieobjekt
+ Mediatyp
+ Medlemsgrupp
+ Medlem av grupp(er)
+ Roll
+ Medlemstyp
+ Inget datum valt
+ Sidnamn
+ Ej medlem av grupp(er)
+ Egenskaper
+ Detta dokument är publicerat men syns inte eftersom den överordnade sidan %0% inte är publicerad
+ Oops: detta dokument är publicerat men finns inte i cacheminnet (internt fel)
+ Publicera
+ Publiceringsstatus
+ Publiceringsdatum
+ Rensa datum
+ Sorteringsordningen har uppdaterats
+ För att sortera noderna, dra i dem eller klicka på någon av kolumnrubrikerna. Du kan markera flera noder samtidigt genom att hålla nere SHIFT eller CONTROL medan du klickar
+ Statistik
+ Mål
+ Titel (valfritt)
+ Typ
+ Avpublicera
+ Avpubliceras
+ Senast redigerad
+ Datum/tid detta dokument ändrats
+ Uppdaterad av
+ Ta bort fil
+ Länk till dokument
+
+
+ Var vill du skapa den nya %0%
+ Skapa innehåll under
+ "dokumenttyper".]]>
+ "mediatyper".]]>
+ Välj typ och rubrik
+
+
+ Surfa på din webbplats
+ - Dölj
+ Om Umbraco inte öppnas kan det bero på att du måste tillåta poppuppfönster att öppnas från denna webbplats
+ har öppnats i ett nytt fönster
+ Starta om
+ Välkommen
+ Besök
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ Namn
+ Hantera domännamn
+ Stäng fönstret
+ Är du säker på att du vill ta bort
+ Är du säker på att du vill avaktivera
+ Kryssa i denna ruta för att bekräfta att %0% objekt tas bort
+ Är du säker?
+ Är du säker?
+ Klipp ut
+ Redigera ord i ordboken
+ Redigera språk
+ Infoga ankarlänk
+ Infoga tecken
+ Infoga grafisk rubrik
+ Infoga bild
+ Lägg in länk
+ Infoga makro
+ Infoga tabell
+ Senast redigerad
+ Länk
+ Intern länk:
+ När du använder lokala länkar, lägg till "#" framför länken
+ Öppna i nytt fönster?
+ Makroinställningar
+ Detta makro innehåller inga egenskaper som du kan redigera
+ Klistra in
+ Redigera rättigheter för
+ Allt som ligger i papperskorgen tas nu bort. Stäng inte detta fönster förrän detta är klart
+ Papperskorgen är nu tom
+ Om du tömmer papperskorgen kommer allt som ligger i den att tas bort permanent
+ regexlib.com's webbtjänst har för närvarande driftsstörningar. Tyvärr kan vi inte göra något åt detta.]]>
+ Sök efter en regular expression som kan validera ett formulärsfält. t.ex. 'email' eller 'url'
+ Ta bort makro
+ Obligatoriskt formulärsfält
+ Webbplatsen har indexerats
+ Cache för webbplatsen har uppdaterats. Allt publicerat innehåll är nu uppdaterat. Innehåll som inte har publicerats är fortfarande opublicerat.
+ Webbplatsens cache kommer att uppdateras. Allt innehåll som är publicerat kommer att uppdateras. Innehåll som inte är publicerat kommer att förbli opublicerat.
+ Antal kolumner
+ Antal rader
+ Ge din platshållare ett id du kan skjuta in innehåll från underordnade sidmallar i platshållaren genom att ge den ett ID. Du refererar sedan till detta ID med hjälp av en <asp:content /> tagg.]]>
+ Välj ett platshållar-ID från listan nedan. Du kan bara välja de ID som finns i denna malls huvudmall.]]>
+ Klicka på förhandsgranskningsbilden för att se bilden i full storlek
+ Välj ett objekt
+ Se cachat objekt
+
+
+ Redigera de olika översättningarna för ordboksinlägget %0% nedan. Du kan lägga till ytterligare språk under 'språk' i menyn till vänster.
+ Språknamn
+
+
+ Tillåt i roten
+ Endast innehållstyper med denna markerad kan skapas i roten av innehåll samt media
+ Tillåtna typer för underliggande noder
+ Dokumenttypssammansättning
+ Skapa
+ Skapa en anpassad listvy
+ Aktuell listvy
+ Datatyp för markerad listvy
+ Ta bort flik
+ Beskrivning
+ Aktivera listvy
+ Konfigurerar innehållet till att visa en sorterbar & sökbar lista av nodens barn, barnen kommer ej att visas i trädet.
+ Ny flik
+ Radera anpassad listvy
+ Flik
+ Miniatyrbild
+
+
+ Lägg till värde
+ Datatyp i databasen
+ Datatyp GUID
+ Rendera som
+ Knappar
+ Slå på avancerade inställningar för
+ Slå på kontextmeny
+ Maximal förinställd storlek för bilder som läggs in
+ Relaterade stilmallar
+ Visa etikett
+ Bredd och höjd
+
+
+ Informationen har sparats, men innan du kan publicera denna sida måste du åtgärda följande fel:
+ Det går inte att byta lösenord i den medlemshanterare du har valt (EnablePasswordRetrieval måste vara satt till 'true').
+ %0% redan finns
+ Följande fel inträffade:
+ Följande fel inträffade:
+ Lösenordet måste bestå av minst %0% tecken varav minst %1% är icke-alfanumeriska tecken (t.ex. %, #, !, @).
+ %0% måste vara ett heltal
+ %0% under %1% är ett obligatoriskt fält
+ %0% är ett obligatoriskt fält
+ %0% under %1% har ett felaktigt format
+ %0% har ett felaktigt format
+
+
+ Även om CodeMirror är aktiverad i konfigurationen, så är den avaktiverad i Internet Explorer på grund av att den inte är tillräckligt stabil
+ Du måste ange både alias och namn för den nya egenskapstypen!
+ Filtypen är icke godkännd av administratören
+ Ett fel upptäcktes i läsningen/skrivningen till den aktuella filen eller mappen
+ Det finns ett konfigurationsfel med datatypen som används för denna egenskapen, vänligen undersök datatypen
+ Du måste skriva en rubrik
+ Du måste välja en typ
+ Du kommer att göra bilden större än originalstorleken. Är du säker på att du vill fortsätta?
+ Ett fel inträffade i pythonscriptet
+ Pythonscriptet har inte sparats eftersom det innehåller ett eller flera fel.
+ Startsidan har tagits bort, var vänlig kontakta administratören
+ Du måste markera något innan du kan göra stiländringar
+ Det finns inga tillgängliga stilar
+ Placera markören i den vänstra av de två celler du vill slå ihop
+ Du kan inte dela en cell som inte är ihopslagen.
+ Fel i XSLT-scriptet
+ XSLT-scriptet har inte sparats eftersom det innehåller ett eller flera fel
+
+
+ Om
+ Åtgärd
+ Händelser
+ Lägg till
+ Alias
+ Är du säker?
+ Kant
+ eller
+ Avbryt
+ Cellmarginal
+ Välj
+ Stäng
+ Stäng fönstret
+ Kommentar
+ Bekräfta
+ Begränsa proportioner
+ Fortsätt
+ Kopiera
+ Skapa
+ Databas
+ Datum
+ Standard
+ Ta bort
+ Borttagen
+ Tar bort...
+ Design
+ Dimensioner
+ Ner
+ Ladda ned
+ Redigera
+ Redigerad
+ Element
+ E-post
+ Fel
+ Hitta
+ Mapp
+ Höjd
+ Hjälp
+ Ikon
+ Importera
+ Innermarginal
+ Lägg in
+ Installera
+ Justera
+ Språk
+ Layout
+ Laddar
+ Låst
+ Logga in
+ Logga ut
+ Logga ut
+ Makro
+ Fler
+ Flytta
+ Namn
+ Nytt
+ Nästa
+ Nej
+ av
+ OK
+ Öppna
+ eller
+ Lösenord
+ Sökväg
+ Platshållar-ID
+ Ett ögonblick...
+ Föregående
+ Egenskaper
+ E-postadress för formulärsdata
+ Papperskorg
+ Återstående
+ Döp om
+ Förnya
+ Obligatorisk
+ Försök igen
+ Rättigheter
+ Sök
+ Sökresultat
+ Server
+ Visa
+ Vilken sida skall visas när formuläret är skickat
+ Storlek
+ Sortera
+ Submit
+ Skriv
+ Skriv för att söka...
+ Upp
+ Uppdatera
+ Uppgradera
+ Ladda upp
+ URL
+ Användare
+ Användarnamn
+ Värde
+ Välkommen...
+ Bredd
+ Titta på
+ Ja
+ Reorder
+ I am done reordering
+
+
+ Bakgrundsfärg
+ Fetstil
+ Textfärg
+ Typsnitt
+ Text
+
+
+ Sida
+
+
+ Installationsprogrammet kan inte ansluta till databasen.
+ Kunde inte spara filen web.config. Vänligen ändra databasanslutnings-inställningarna manuellt.
+ Din databas har lokaliserats och är identifierad som
+ Databaskonfiguration
+ installera]]>
+ Nästa för att fortsätta.]]>
+ Databasen kunde inte hittas! Kontrollera att informationen i databasanslutnings-inställningarna i filen "web.config" är rätt.
För att fortsätta måste du redigera filen "web.config" (du kan använda Visual Studio eller din favorit text-redigerare), bläddra till slutet, lägg till databasanslutnings-inställningarna för din databas i fältet som heter "umbracoDbDSN" och spara filen.
]]>
+ Eventuellt kan du behöva kontakta ditt webb-hotell. Om du installerar på en lokal maskin eller server kan du få informationen från din systemadministratör.]]>
+ Tryck Uppgradera knappen för att uppgradera din databas till Umbraco %0%
Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt!
]]>
+ Tryck Nästa för att fortsätta.]]>
+ Nästa för att fortsätta med konfigurationsguiden]]>
+ Lösenordet på standardanvändaren måste bytas!]]>
+ Standardanvändaren har avaktiverats eller har inte åtkomst till Umbraco!
Du behöver inte göra något ytterligare här. Klicka Next för att fortsätta.]]>
+ Standardanvändarens lösenord har ändrats sedan installationen!
Du behöver inte göra något ytterligare här. Klicka Nästa för att fortsätta.]]>
+ Lösenordet är ändrat!
+ Umbraco skapar en standardanvändare med login ('admin') och lösenordet ('default'). Det är viktigt att lösenordet ändras till något unikt.
Det här steget kommer kontrollera standardanvändarens lösenord och låta dig vet om det behöver ändras.
]]>
+ Få en flygande start, kolla på våra introduktionsvideor
+ Genom att klicka på Nästa knappen (eller redigera UmbracoConfigurationStatus i web.config), accepterar du licensavtalet för den här mjukvaran som det är skrivet i rutan nedan. Observera att den här Umbracodistributionen består av två olika licensavtal, "the open source MIT license" för ramverket och "the Umbraco freeware license" som täcker användargränssnittet.
+ Inte installerad än.
+ Berörda filer och mappar
+ Här hittar du mer information om att sätta rättigheter för Umbraco
+ Du måste ge ASP.NET ändra rättigheter till följande filer/mappar
+ Dina rättighetsinställningar är nästa perfekta!
+
Du kan köra Umbraco utan problem, men du kommer inte att kunna installera paket vilket är rekommenderat för att kunna utnyttja Umbraco fullt ut.]]>
+ Hur skall man lösa
+ Klicka här för att läsa text-versionen
+ video-självstudiekurs om hur du konfigurerar mapp-rättigheter för Umbraco eller läs text-versionen.]]>
+ Dina rättighetsinställningar kan vara ett problem!
Du kan köra Umbraco utan problem, men du kommer inte att kunna skapa mappar eller installera paket vilket är rekommenderat för att kunna utnyttja Umbraco fullt ut.]]>
+ Dina rättighetsinställningar är inte reda för Umbraco!
För att kunna köra Umbraco måste du ändra dina rättighetsinställningar.]]>
+ Dina rättighetsinställningar är perfekta!
Du är redo att köra Umbraco och installera paket!]]>
+ Lösa mapp problem
+ Följ den här länken för mer information om problem med ASP.NET och att skapa mappar
+ Konfigurerar mapprättigheter
+ Umbraco behöver skriv/ändra rättigheter till vissa mappar för att spara filer som bilder och PDFer. Umbraco sparar också temporär data (så kallad cache) för att öka prestandan på din webbplats.
+ Jag vill börja från början
+ lär dig hur) Du kan fortfarande välja att installera Runway senare. Gå in i Utvecklarsektionen och välj Paket.]]>
+ Du har just installerat en ren Umbraco platform. Vad vill du göra härnäst?
+ Runway är installerat
+ Det här är vår lista över rekommenderade moduler, markera de moduler du vill installera, eller visa den fullständiga listan]]>
+ Endast rekommenderad för erfarna användare
+ Jag vill börja med en enkel webbplats
+ "Runway" är en enkel webbplats med några enkla dokumentyper och mallar. Installationsguiden kan automatiskt installera Runway åt dig, men du kan lätt ändra, utöka eller ta bort den. Det är inte nödvändigt och du kan använda Umbraco utan den, men Runway erbjuder en enkel grund baserad på bästa praxis för att hjälpa dig igång snabbare än någonsin tidigare. Om du väljer att installera Runway, kan du välja till grundläggande byggstenar så kallade Runway-moduler för att utöka dina Runway-sidor. Inkluderat i Runway: Startsida, "komma igång"-sida och en sidan om att installera moduler. Tilläggs moduler: Toppnavigation, Sitemap, Kontakt och galleri. ]]>
+ Vad är Runway
+ Steg 1/5 Acceptera licensavtalet
+ Steg 2/5: Databaskonfiguration
+ Steg 3/5: Bekräftar filrättigheter
+ Steg 4/5: Umbraco säkerhetskontroll
+ Steg 5/5: Umbraco är redo att ge dig en flygande start
+ Tack för att du valde Umbraco
+ Besök din nya webbplats Du installerade Runway, så varför inte se hur din nya webbplats ser ut.]]>
+ Ytterligare hjälp och information Få hjälp från våra prisbelönta community, bläddra i dokumentationen eller titta på några gratis videor om hur man bygger en enkel webbplats, hur du använder paket eller en snabbguide till Umbracos terminologi]]>
+ Umbraco %0% är installerat och klart för användning
+ /web.config filen och ändra AppSettingsnyckeln UmbracoConfigurationStatus på slutet till %0%]]>
+ börja omedelbart genom att klicka på "Starta Umbraco"-knappen nedan. Om du är en ny Umbraco användarekan du hitta massor av resurser på våra kom igång sidor.]]>
+ Starta Umbraco För att administrera din webbplats öppnar du bara Umbraco back office och börjar lägga till innehåll, uppdatera mallar och stilmallar eller lägga till nya funktioner.]]>
+ Anslutningen till databasen misslyckades.
+ Se
+ Umbraco %0% antingen för en ny installation eller en uppgradering från version 3.0.
]]>
+ [%0%] Meddelande för att informera om att %1% har utförts på %2%
+ Notifieringar
+
+
+ och leta upp paketet. Umbracos installationspaket har oftast filändelsen ".umb" eller ".zip".]]>
+ Utvecklare
+ Demonstration
+ Dokumentation
+ Paket metadata
+ Paketnamn
+ Paketet innehåller inga poster
+ Det är säkert att ta bort den ur systemet genom att klicka på "avinstallera paket" nedan.]]>
+ Inga uppdateringar tillgängliga
+ Paketalternativ
+ Paket läsmig
+ Paketvalv
+ Bekräfta avinstallation
+ Paketet har avinstallerats
+ Paketet har avinstallerats utan problem
+ Avinstallera paket
+ OBS! dokument, media osv som använder de borttagna posterna kommer sluta fungera vilket kan leda till att systemet blir instabilt. Avinstallera därför med försiktighet. Om du är osäker, kontakta personen som skapat paketet.]]>
+ Hämta uppdatering från paketvalvet
+ Uppdatera paket
+ Uppdateringsinstruktioner
+ Det finns an uppdaterad version av paketet. Du kan hämta den direkt från Umbracos paketvalv.
+ Paketversion
+ Versionshistorik för paket
+ Besök paketets webbplats
+
+
+ Klistra in med helt bibehållen formatering (rekommenderas ej)
+ Texten du försöker klistra in innehåller specialtecken och/eller formateringstaggar. Detta kan bero på att texten kommer från t.ex. Microsoft Word. Umbraco kan ta bort specialtecken och formateringstaggar automatiskt så att innehållet lämpar sig bättre för webbpublicering.
+ Klistra in texten helt utan formatering
+ Klistra in texen och ta bort specialformatering (rekommenderas)
+
+
+ Fyll i ett namn...
+ Skriv för att filtrera...
+ Namnge %0%...
+ Fyll i ditt lösenord
+ Skriv för att söka...
+ Fyll i ditt lösenord
+ Skriv för att lägga till taggar (och tryck enter efter varje tagg)...
+
+
+ Rollbaserat lösenordsskydd
+ Då används Umbracos medlemsgrupper.]]>
+ rollbaserat lösenordsskydd.]]>
+ Sida med felmeddelande
+ Används när en användare är inloggad, men saknar rättigheter att se sidan
+ Välj hur du vill lösenordsskydda sidan
+ %0% är nu lösenordsskyddad
+ Lösenordsskyddet är nu borttaget på %0%
+ Inloggningssida
+ Välj sidan med inloggningsformuläret
+ Ta bort lösenordsskydd
+ Välj sidorna med inloggningsformulär och felmeddelande
+ Välj de roller som ska ha tillgång till denna sida
+ Ange användarnamn och lösenord för denna sida
+ Samma lösenord för alla användare
+ Välj detta alternativ om du vill skydda sidan med ett enkelt användarnamn och lösenord. Alla loggar då in med samma inloggningsuppgifter.
+
+
+ %0% kunde inte publiceras på grund av dess tidsinställda publicering.
+ %0% kunde inte publiceras på grund av att ett tredjepartstillägg avbröt publiceringen.
+ %0% kan inte publiceras, på grund av att överordnad nod inte är publicerad.
+ %0% kunde inte publiceras på grund av följande orsaker: %1% passerade inte valideringen.
+ Inkludera opublicerade undersidor
+ Publicering pågår - vänligen vänta...
+ %0% av %1% sidor har publicerats...
+ %0% har publicerats
+ %0% och underliggande sidor har publicerats
+ Publicera %0% och alla dess underordnade sidor
+ ok för att publicera %0%. Därmed blir innehållet publikt.
Du kan publicera denna sida och alla dess undersidor genom att kryssa i publicera alla undersidor. ]]>
+
+
+ ange en extern länk
+ ange en intern sida
+ Rubrik
+ Länk
+ Öppna i nytt fönster
+ Ange visningstext
+ Ange adress
+
+
+ Återställ
+
+
+ Nuvarande version
+ Röd text kommer inte att synas i den valda versionen. , Grön betyder att den har tillkommit]]>
+ Dokumentet har återgått till en tidigare version
+ Här visas den valda sidversionen i HTML. Om du vill se skillnaden mellan två versioner samtidigt, välj istället "Diff".
+ Återgå till
+ Vald version
+ Visningsläge
+
+
+ Redigera script
+
+
+ Analytics
+ Concierge
+ Innehåll
+ Courier
+ Utvecklare
+ Formulär
+ Hjälp
+ Umbraco konfigurationsguide
+ Media
+ Medlemmar
+ Nyhetsbrev
+ Inställningar
+ Statistik
+ Översättning
+ Användare
+
+
+ som huvudinnehållstyp. Tabbar från huvudinnehållstyper visas inte och kan endast redigeras på själva huvudinnehållstypen.
+ Huvudinnehållstyp påslagen
+ Denna huvudinnehållstyp använder
+ Defaultmall
+ Ordboksnyckel
+ För att importera en dokumenttyp, leta upp ".udt"-filen på din hårddisk genom att klicka på "Browse"-knappen och sedan på "Importera" (du får bekräfta ditt val i nästa steg).
+ Namn på ny flik
+ Nodtyp
+ Inga egenskaper definierade i denna sektion. Klicka på "Lägg till ny egenskap" länken vid toppen för att skapa en ny egenskap.
+ Typ
+ Skript
+ Stilmall
+ Egenskap för stilmall
+ Flik
+ Fliknamn
+ Flikar
+ Huvuddokumenttyp
+ Skapa matchande mall
+
+
+ Sort order
+ Creation date
+ Sortering klar
+ Välj i vilken ordning du vill ha sidorna genom att dra dem upp eller ner i listan. Du kan också klicka på kolumnrubrikerna för att sortera grupper av sidor
+ Stäng inte fönstret under tiden sidorna sorteras.]]>
+
+
+ Publiceringen avbröts av ett tredjepartstillägg
+ Egenskapstyp finns redan
+ Egenskapstyp skapad
+ Datatyp: %1%]]>
+ Egenskapstypen har tagits bort
+ Innehållstypen har sparats
+ Ny flik skapad
+ Fliken har tagits bort
+ Fliken med id: %0% har tagits bort
+ Innehållet är avpublicerat
+ Stilmallen kunde inte sparas
+ Stilmallen sparades
+ Stilmallen sparades utan fel
+ Datatypen har sparats
+ Ordet sparades i ordboken
+ Det gick inte att publicera sidan eftersom dess överordnade sida inte är publicerad
+ Innehållet är publicerat
+ och syns på webbplatsen
+ Innehållet har sparats
+ Kom ihåg att publicera för att ändringarna ska synas på webbplatsen
+ Skickat för godkännande
+ Ändringarna har skickats för godkännande
+ Mediaobjektet är sparat
+ Media sparat
+ Medlemmen har sparats
+ Egenskap för stilmall har sparats
+ Stilmallen har sparats
+ Sidmallen har sparats
+ Ett fel inträffade när användaren sparades (läs logg-filen)
+ Användaren har sparats
+ Användartypen har sparats
+ Filen sparades inte
+ filen kunde inte sparas. Kontrollera filrättigheterna
+ Filen har sparats
+ Filen sparades utan fel
+ Språket har sparats
+ Partial view Ej sparad
+ Ett fel uppstod när filen sparades
+ Partial view sparad
+ Partial view sparad utan fel!
+ Pythonscriptet har inte sparats
+ Pythonscriptet kunde inte sparas på grund av ett fel
+ Pythonscriptet har sparats
+ Inga fel i pythonscriptet
+ Sidmallen har inte sparats
+ Kontrollera att du inte har två sidmallar med samma alias
+ Sidmallen har sparats
+ Sidmallen sparades utan fel
+ XSLT-scriptet sparades inte
+ XSLT-scriptet innehöll ett fel
+ XSLT-scripet kunde inte sparas, kontrollera filrättigheterna
+ XSLT-scriptet har sparats
+ Inga fel i XSLT-scriptet
+
+
+ Använder CSS-syntax, t ex: h1, .redHeader, .blueTex
+ Redigera stilmall
+ Redigera egenskaper för stilmall
+ Namnet som används för att identifiera stilen i HTML-editorn
+ Förhandsgranska
+ Stilar
+
+
+ Redigera sidmall
+ Lägg in innehållsyta
+ Lägg in platshållare för innehållsyta
+ Lägg in ord från ordboken
+ Lägg in makro
+ Lägg in sidfält
+ Huvudmall
+ Snabbguide för taggar i Umbracos sidmallar
+ Sidmall
+
+
+ Lägg till
+ Choose layout
+ Lägg till rad
+ Add content
+ Drop content
+ Styles applied
+ Indholdet er ikke tilladt her
+ Indholdet er tilladt her
+ Klicka för att lägga in
+ Klicka för att lägga till bild
+ Bildtext...
+ Skriv här...
+ Rutnätslayouter
+ Layouter är arbetsytan för rutnätet, oftast så behöver du bara en eller två layouter
+ Lägg till layout
+ Redigera layouten genom att sätta kolumnbredd och lägg till fler sektioner
+ Radkonfigureringar
+ Rader är fördefinierade celler, arrangerade horisontellt
+ Lägg till radkonfiguration
+ Justera rad genom att ställa in cellbredder och lägga till ytterligare celler
+ Kolumner
+ Sammanlagda antalet kolumner i rutnätslayout
+ Inställningar
+ Konfigurera vilka inställningar redaktörer kan ändra
+ Stilar
+ Konfigurera vilken styling redaktörer kan ändra
+ Inställningarna kommer bara att sparas om det inmatade json-konfigurationen är giltig
+ Tillåt alla editors
+ Tillåt alla rad- konfigurationer
+
+
+ Alternativt fält
+ Alternativ text
+ Casing
+ Välj fält
+ Konvertera radbrytningar
+ Byter radbrytningar mot html-taggen <br>
+ Anpassade fält
+ Ja, endast datum
+ Omkodning
+ Formatera som datum
+ HTML-omkodning
+ Ersätter specialtecken med deras HTML-motsvarigheter.
+ Kommer läggas till efter fältets värde
+ Kommer läggas till före fältets värde
+ Gemener
+ Ingen
+ Infoga efter fält
+ Infoga före fält
+ Rekursiv
+ Avlägsna stycke-taggar
+ Kommer att avlägsna alla <P> i början och slutet av texten
+ Standardfält
+ Versaler
+ URL-koda
+ Om fältets innehåll skall sändas till en url, skall detta slås på så att specialtecken kodas
+ Texten kommer användas om ovanstående fält är tomma
+ Fältet kommer användas om det primära fältet ovan är tomt
+ Ja, med tid. Separator:
+
+
+ Arbetsuppgifter som tilldelats dig
+ som har tilldelats dig. För att se en detaljvy med kommentarer, klicka på "Detaljer" eller på sidans namn. Du kan också ladda ned sidan i XML-format genom att klicka på länken "Ladda ned XML". För att markera ett översättningsjobb som avslutat, gå till detaljvyn och klicka på knappen "Stäng".]]>
+ Stäng arbetsuppgift
+ Översättningsdetaljer
+ Ladda ned alla översättningsjobb i XML-format
+ Ladda ned i XML-format
+ Ladda hem DTD för XML
+ Fält
+ Inkludera undersidor
+ Hej %0%. Detta är ett automatisk mail skickat for att informera dig om att det finns en översättningsförfrågan på dokument '%1%' till '%5%' skickad av %2%. För att redigera, besök http://%3%/translation/details.aspx?id=%4%. För att få en översikt över dina översättningsuppgigter loggar du in i Umbraco på: http://%3%
+ [%0%] Översättningsuppgift för %1%
+ Hittade inga användare som är översättare. Vänligen skapa en användare som är översättare innan du börjar skicka innehåll för översättning
+ Arbetsuppgifter som du har skapat
+ som du har skapat. För att se en detaljvy med kommentarer, klicka på "Detaljer" eller på sidans namn. Du kan också ladda ned sidan i XML-format genom att klicka på länken "Ladda ned XML". För att markera ett översättningsjobb som avslutat, gå till detaljvyn och klicka på knappen "Stäng".]]>
+ Sidan %0% har skickats för översättning
+ Skicka sidan %0% för översättning
+ Tilldelat av
+ Arbetsuppgift öppnad
+ Totalt antal ord
+ Översätt till
+ Översättning klar.
+ Du kan förhandsgranska sidorna du nyss har översatt genom att klicka nedan. Om originalsidan finns kommer du att se en jämförelse mellan din sida och originalsidan.
+ Översättningen misslyckades. XML-filen kan vara korrupt
+ Valmöjligheter översättning
+ Översättare
+ Ladda upp översättning i XML-format
+
+
+ Analytics
+ Cacha webbläsare
+ Papperskorg
+ Skapade paket
+ Datatyper
+ Ordbok
+ Installerade paket
+ Installera skin
+ Installera Startkit
+ Språk
+ Installera lokalt paket
+ Makron
+ Mediatyper
+ Medlem
+ Medlemsgrupper
+ Roller
+ Medlemstyper
+ Dokumenttyper
+ Paket
+ Paket
+ Python-filer
+ Installera från gemensamt bibliotek
+ Installera Runway
+ Runway-moduler
+ Skript-filer
+ Skript
+ Stilmallar
+ Sidmallar
+ XSLT-filer
+
+
+ Ny uppdatering tillgänglig
+ %0% är klart, klicka här för att ladda ner
+ Ingen kontakt med server
+ Fel vid kontroll av uppdatering. Se trace-stack för mer information.
+
+
+ Administratör
+ Kategorifält
+ Ändra lösenord
+ Du kan byta ditt lösenord för Umbraco Back Office genom att fylla i nedanstående formulär och klicka på knappen "Ändra lösenord".
+ Bekräfta det nya lösenordet
+ Innehållskanal
+ Fält för beskrivning
+ Avaktivera användare
+ Dokumenttyp
+ Redaktör
+ Fält för utdrag
+ Språk
+ Login
+ Startnod i mediabiblioteket
+ Sektioner
+ Byt ditt lösenord
+ Inaktivera tillgång till Umbraco
+ Lösenord
+ Ditt lösenord är nu ändrat!
+ Vänligen bekräfta ditt nya lösenord
+ Nuvarande lösenord
+ Vänligen fyll i ditt nya lösenord
+ Nuvarande lösenord är ogiltigt
+ Ditt nya lösenord kan inte vara tomt!
+ Lösenorden matchar inte. Vänligen försök igen!
+ Det bekräftade lösenordet matchar inte det nya lösenordet!
+ Ersätt rättigheterna på underliggande noder
+ Du redigerar nu rättigheterna för sidorna:
+ Välj de sidor vars rättigheter du vill redigera
+ Återställ lösenord
+ Sök igenom alla undernoder
+ Sessionen går ut
+ Startnod i innehåll
+ Användarens namn
+ Användarrättigheter
+ Användartyp
+ Användartyper
+ Skribent
+ Din nuvarande historik
+ Översättare
+ Din profil
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Config/Lang/zh.xml b/WebCms/Umbraco/Config/Lang/zh.xml
new file mode 100644
index 0000000..0ab2a54
--- /dev/null
+++ b/WebCms/Umbraco/Config/Lang/zh.xml
@@ -0,0 +1,1003 @@
+
+
+
+ 孙柱梁
+ http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files
+
+
+ 管理主机名
+ 跟踪审计
+ 浏览节点
+ 改变文档类型
+ 复制
+ 创建
+ 创建扩展包
+ 删除
+ 禁用
+ 清空回收站
+ 导出文档类型
+ 导入文档类型
+ 导入扩展包
+ 实时编辑模式
+ 退出
+ 移动
+ 提醒
+ 公众访问权限
+ 发布
+ 重新加载节点
+ 重新发布整站
+ 权限
+ 回滚
+ 提交至发布者
+ 发送给翻译
+ 排序
+ 提交至发布者
+ 翻译
+ 取消发布
+ 更新
+
+
+ 禁止访问
+ 添加域名
+ 移除
+ 错误的节点
+ 域名重复
+ 域名
+ 语言
+ 新域名 '%0%' 已创建
+ 域名 '%0%' 已删除
+ 域名 '%0%' 已使用
+
+
+ https://www.example.com/、example.com/en、……使用 * 代表任意域名,
+ 只需要设置语言部分即可。]]>
+
+ 域名 '%0%' 已更新
+ 域名错误
+ 编辑当前域名
+ 继承
+ 语言
+
+ 也可以从父节点继承。]]>
+
+ 域名
+
+
+ 查看
+
+
+ 粗体
+ 取消段落缩进
+ 插入表单字段
+ 插入图片标题
+ 编辑Html
+ 段落缩进
+ 斜体
+ 居中
+ 左对齐
+ 右对齐
+ 插入链接
+ 插入本地链接(锚点)
+ 圆点列表
+ 数字列表
+ 插入宏
+ 插入图片
+ 编辑关联
+ 保存
+ 保存并发布
+ 保存并提交审核
+ 预览
+ 因未设置模板无法预览
+ 选择样式
+ 显示样式
+ 插入表格
+
+
+ 要更改所选节点的文档类型,先在列表中选择合适的文档类型。
+ 然后设置当前文档类型到新文档类型的各字段间的对应映射关系并保存。
+ 内容已被重新发布
+ 当前属性
+ 当前类型
+ 不能改变文档类型,因为没有可替代的类型。
+ 文档类型已更改
+ 要映射的字段
+ 映射字段
+ 新模板
+ 新类型
+ 无
+ 内容
+ 选择新的文档类型
+ 选中文档的类型已被成功更改为[new type],以下字段被映射:
+ 至
+ 不能完成字段映射,因为存在一个字段映射至多字段的问题。
+ 仅显示可作为替代的文档类型。
+
+
+ 关于本页
+ 别名
+ (图片的替代文本)
+ 替代链接
+ 点击编辑
+ 创建者
+ 创建时间
+ 文档类型
+ 编辑
+ 过期于
+ 该项发布之后有更改
+ 该项没有发布
+ 最近发布
+ 媒体链接地址
+ 媒体类型
+ 会员组
+ 角色
+ 会员类型
+ 没有选择时间
+ 页标题
+ 属性
+ 该文档不可见,因为其上级 '%0%' 未发布。
+ 该文档已发布,但是没有更新至缓存(内部错误)
+ 发布
+ 发布状态
+ 发布于
+ 清空时间
+ 排序完成
+ 拖拽项目或单击列头即可排序,可以按住Shift多选。
+ 统计
+ 标题(可选)
+ 类型
+ 取消发布
+ 最近编辑
+ 移除文件
+ 链接到文档
+ 会员组成员
+ 非会员组成员
+
+
+ 您想在哪里创建 %0%
+ 创建在
+ 选择类型和标题
+
+
+ 浏览您的网站
+ - 隐藏
+ 如果Umbraco没有打开,您可能需要允许弹出式窗口。
+ 已经在新窗口中打开
+ 重启
+ 访问
+ 欢迎
+
+
+ Stay
+ Discard changes
+ You have unsaved changes
+ Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+ Done
+
+ Deleted %0% item
+ Deleted %0% items
+ Deleted %0% out of %1% item
+ Deleted %0% out of %1% items
+
+ Published %0% item
+ Published %0% items
+ Published %0% out of %1% item
+ Published %0% out of %1% items
+
+ Unpublished %0% item
+ Unpublished %0% items
+ Unpublished %0% out of %1% item
+ Unpublished %0% out of %1% items
+
+ Moved %0% item
+ Moved %0% items
+ Moved %0% out of %1% item
+ Moved %0% out of %1% items
+
+ Copied %0% item
+ Copied %0% items
+ Copied %0% out of %1% item
+ Copied %0% out of %1% items
+
+
+ 锚点名称
+ 管理主机名
+ 关闭窗口
+ 您确定要删除吗
+ 您确定要禁用吗
+ 单击此框确定删除%0%项
+ 您确定吗?
+ 您确定吗?
+ 剪切
+ 编辑字典项
+ 编辑语言
+ 插入本地链接
+ 插入字符
+ 插入图片标题
+ 插入图片
+ 插入链接
+ 插入宏
+ 插入表格
+ 最近编辑
+ 链接
+ 内部链接:
+ 本地链接请用“#”号开头
+ 在新窗口中打开?
+ 宏设置
+
+ 粘贴
+ 编辑权限
+ 正在清空回收站,请不要关闭窗口。
+ 回收站已清空
+ 从回收站删除的项目将不可恢复
+ regexlib.com的服务暂时出现问题。]]>
+ 查找正则表达式来验证输入,如: 'email、'zip-code'、'url'。
+ 移除宏
+ 必填项
+ 站点已重建索引
+ 网站缓存已刷新,所有已发布的内容更新生效。
+ 网站缓存将会刷新,所有已发布的内容将会更新。
+ 表格列数
+ 表格行数
+ 设置一个占位符id 您可以在子模板中通过该ID来插入内容,引用格式: <asp:content />。]]>
+ 选择一个占位符id。]]>
+ 点击图片查看完整大小
+ 拾取项
+ 查看缓存项
+
+
+ %0%’ 您可以在左侧的“语言”中添加一种语言]]>
+ 语言名称
+
+
+ 允许子项节点类型
+ 创建
+ 删除选项卡
+ 描述
+ 新建选项卡
+ 选项卡
+ 缩略图
+
+
+ 添加预设值
+ 数据库数据类型
+ 数据类型唯一标识
+ 渲染控件
+ 按钮
+ 允许高级设置
+ 允许快捷菜单
+ 插入图片默认最大
+ 关联的样式表
+ 显示标签
+ 宽和高
+
+
+ 数据已保存,但是发布前您需要修正一些错误:
+ 当前成员提供程序不支持修改密码(EnablePasswordRetrieval的值应该为true)
+ %0% 已存在
+ 发现错误:
+ 发现错误:
+ 密码最少%0%位,且至少包含%1%位非字母数字符号
+ %0% 必须是整数
+ %1% 中的 %0% 字段是必填项
+ %0% 是必填项
+ %1% 中的 %0% 格式不正确
+ %0% 格式不正确
+
+
+ 该文件类型已被管理员禁用
+ 注意,尽管配置中允许CodeMirror,但是它在IE上不够稳定,所以无法在IE运行。
+ 请为新的属性类型填写名称和别名!
+ 权限有问题,访问指定文件或文件夹失败!
+ 请输入标题
+ 请选择类型
+ 图片尺寸大于原始尺寸不会提高图片质量,您确定要把图片尺寸变大吗?
+ python脚本错误
+ python脚本未保存,因为包含错误。
+ 默认打开页面不存在,请联系管理员
+ 请先选择内容,再设置样式。
+ 没有可用的样式
+ 请把光标放在您要合并的两个单元格中的左边单元格
+ 非合并单元格不能分离。
+ XSLT源码出错
+ XSLT未保存,因为包含错误。
+
+
+ 关于
+ 操作
+ 添加
+ 别名
+ 您确定吗?
+ 边框
+ 或
+ 取消
+ 单元格边距
+ 选择
+ 关闭
+ 关闭窗口
+ 备注
+ 确认
+ 强制属性
+ 继续
+ 复制
+ 创建
+ 数据库
+ 时间
+ 默认
+ 删除
+ 已删除
+ 正在删除…
+ 设计
+ 规格
+ 下
+ 下载
+ 编辑
+ 已编辑
+ 元素
+ 邮箱
+ 错误
+ 查找文档
+ 文件夹
+ 高
+ 帮助
+ 图标
+ 导入
+ 内边距
+ 插入
+ 安装
+ 对齐
+ 语言
+ 布局
+ 加载中
+ 锁定
+ 登录
+ 退出
+ 注销
+ 宏
+ 移动
+ 名称
+ 新的
+ 下一步
+ 否
+ 属于
+ 确定
+ 打开
+ 或
+ 密码
+ 路径
+ 占位符ID
+ 请稍候…
+ 上一步
+ 属性
+ 接收数据邮箱
+ 回收站
+ 保持状态中
+ 重命名
+ 更新
+ 重试
+ 权限
+ 搜索
+ 服务器
+ 显示
+ 在发送时预览
+ 大小
+ 排序
+ Submit
+ 类型
+ 输入内容开始查找…
+ 上
+ 更新
+ 更新
+ 上传
+ 链接地址
+ 用户
+ 用户名
+ 值
+ 查看
+ 欢迎…
+ 宽
+ 是
+ Reorder
+ I am done reordering
+
+
+ 背景色
+ 粗体
+ 前景色
+ 字体
+ 文本
+
+
+ 页面
+
+
+ 无法连接到数据库。
+ 无法保存web.config文件,请手工修改。
+ 发现数据库
+ 数据库配置
+ 安装进行 %0% 数据库配置]]>
+ 下一步继续。]]>
+ 数据库未找到!请检查数据库连接串设置。
+
The following list shows the Public Properties from the
+ Control. By checking the Properties and click the "Save Properties" button at
+
+ the bottom, umbraco will create the corresponding Macro Elements.
+
+
+
+
+
+
+
+
The following Macro Parameters was added:
+
+
+ Important: You might need to reload the macro to see the changes.
Choose the repository you want to submit the package to
+
+
+
+
+
+
+
+
+
+
Please enter your credentials to authenticate your user.
+
If you do not have a user on the umbraco package repository, you can create one here.
+
If you do not have a user on this private repository, contact your repository administrator to gain access
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Upload additional documentation for your package to help new users getting started with your package
+
+
+
+
+
+
+
+
+
+
+
+
+
By clicking "submit package" below you understand that your package will be submitted to a package repository and will in some cases be publicly available to download.
+
Please notice: only packages with complete read-me, author information and install information gets considered for inclusion.
+
The package administrators group reservers the right to decline packages based on lack of documentation, poorly written readme and missing author information
+ Remember: .xslt and .ascx files for your macros
+ will be added automaticly, but you will still need to add assemblies,
+ images and script files manually to the list below.
+
+ Here you can add custom installer / uninstaller events to perform certain tasks
+ during installation and uninstallation.
+
+ All actions are formed as a xml node, containing data for the action to be performed.
+ Package actions documentation
+
+ This repository requires authentication before you can download any packages from
+ it.
+ Please enter email and password to login.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Please note: Installing a package containing several items and
+ files can take some time. Do not refresh the page or navigate away before, the installer
+ notifies you once the install is completed.
+
+ This package contains .NET code. This is not unusual as .NET code
+ is used for any advanced functionality on an Umbraco powered website.
+
+ However, if you don't know the author of the package or are unsure why this package
+ contains these files, it is adviced not to continue the installation.
+
+ This package contains legacy property editors which are not compatible with Umbraco 7
+
+ This package may not function correctly if the package developer has not indicated that
+ it is compatible with version 7. Any DataTypes this package creates that do not have
+ a Version 7 compatible property editor will be converted to use a Label/NoEdit property editor.
+
+ This package contains .NET binary files that might not be compatible with this version of Umbraco.
+ If you aren't sure what these errors mean or why they are listed please contact the package creator.
+
+ This package contains one or more macros which have the same alias as an existing one on your site, based on the Macro Alias.
+
+
+ If you choose to continue your existing macros will be replaced with the ones from this package. If you do not want to overwrite your existing macros you will need to change their alias.
+
+ This package contains one or more templates which have the same alias as an existing one on your site, based on the Template Alias.
+
+
+ If you choose to continue your existing template will be replaced with the ones from this package. If you do not want to overwrite your existing templates you will need to change their alias.
+
+ This package contains one or more stylesheets which have the same alias as an existing one on your site, based on the Stylesheet Name.
+
+
+ If you choose to continue your existing stylesheets will be replaced with the ones from this package. If you do not want to overwrite your existing stylesheets you will need to change their name.
+
+
+ The Stylesheets in question:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Installing package, please wait...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ All items in the package have been installed
+
+ Overview of what was installed can be found under "installed package" in the developer
+ section.
+ Umbraco v
+
+ Copyright 2001 -
+
+ Umbraco / Niels Hartvig
+ Developed by the Umbraco Core
+ Team
+
+
+ Umbraco is licensed under the open source license MIT
+
+ Visit umbraco.org
+ for more information.
+
+ Dedicated to Gry, August, Villum and Oliver!
+
+
+
+
+ <%--
+ No thanks, do not install a starterkit!
+ --%>
+
+
+
+
+
+
">
+
+
+
Oops...the installer can't connect to the repository
+ Starter Kits could not be fetched from the repository as there was no connection - which can occur if you are using a proxy server or firewall with certain configurations,
+ or if you are not currently connected to the internet.
+
+ Click Continue to complete the installation then navigate to the Developer section of your Umbraco installation
+ where you will find the Starter Kits listed in the Packages tree.
+
+
+
+
+
+
+
+
+
+
+
Oops...the installer encountered an error
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Install/Views/Index.cshtml b/WebCms/Umbraco/Install/Views/Index.cshtml
new file mode 100644
index 0000000..666f6a5
--- /dev/null
+++ b/WebCms/Umbraco/Install/Views/Index.cshtml
@@ -0,0 +1,73 @@
+@using Umbraco.Web
+@using Umbraco.Web.Install.Controllers
+@{
+ Layout = null;
+}
+
+
+
+
+
+
+
+ Install Umbraco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
A server error occurred
+
This is most likely due to an error during application startup
+
+
+
+
+
+
+
+
+
Did you know
+
+
+
+
{{installer.feedback}}
+
+
+
There has been a problem with the build.
+
This might be because you could be offline or on a slow connection. Please try the following steps
',
+ replace: true,
+ compile: function () {
+ return function (scope, elm, attrs, ngModel) {
+
+ scope.value = ngModel.$viewValue;
+
+ function parseNumber(n, decimals) {
+ return (decimals) ? parseFloat(n) : parseInt(n);
+ };
+
+ var options = angular.extend(scope.$eval(attrs.uiSlider) || {}, uiSliderConfig);
+ // Object holding range values
+ var prevRangeValues = {
+ min: null,
+ max: null
+ };
+
+ // convenience properties
+ var properties = ['min', 'max', 'step'];
+ var useDecimals = (!angular.isUndefined(attrs.useDecimals)) ? true : false;
+
+ var init = function () {
+ // When ngModel is assigned an array of values then range is expected to be true.
+ // Warn user and change range to true else an error occurs when trying to drag handle
+ if (angular.isArray(ngModel.$viewValue) && options.range !== true) {
+ console.warn('Change your range option of ui-slider. When assigning ngModel an array of values then the range option should be set to true.');
+ options.range = true;
+ }
+
+ // Ensure the convenience properties are passed as options if they're defined
+ // This avoids init ordering issues where the slider's initial state (eg handle
+ // position) is calculated using widget defaults
+ // Note the properties take precedence over any duplicates in options
+ angular.forEach(properties, function (property) {
+ if (angular.isDefined(attrs[property])) {
+ options[property] = parseNumber(attrs[property], useDecimals);
+ }
+ });
+
+ elm.find(".slider").slider(options);
+ init = angular.noop;
+ };
+
+ // Find out if decimals are to be used for slider
+ angular.forEach(properties, function (property) {
+ // support {{}} and watch for updates
+ attrs.$observe(property, function (newVal) {
+ if (!!newVal) {
+ init();
+ elm.find(".slider").slider('option', property, parseNumber(newVal, useDecimals));
+ }
+ });
+ });
+ attrs.$observe('disabled', function (newVal) {
+ init();
+ elm.find(".slider").slider('option', 'disabled', !!newVal);
+ });
+
+ // Watch ui-slider (byVal) for changes and update
+ scope.$watch(attrs.uiSlider, function (newVal) {
+ init();
+ if (newVal != undefined) {
+ elm.find(".slider").slider('option', newVal);
+ elm.find(".ui-slider-handle").html("" + ui.value + "px")
+ }
+ }, true);
+
+ // Late-bind to prevent compiler clobbering
+ $timeout(init, 0, true);
+
+ // Update model value from slider
+ elm.find(".slider").bind('slidestop', function (event, ui) {
+ ngModel.$setViewValue(ui.values || ui.value);
+ scope.$apply();
+ });
+
+ elm.bind('slide', function (event, ui) {
+ event.stopPropagation();
+ elm.find(".slider-input").val(ui.value);
+ elm.find(".ui-slider-handle").html("" + ui.value + "px")
+ });
+
+ // Update slider from model value
+ ngModel.$render = function () {
+ init();
+ var method = options.range === true ? 'values' : 'value';
+
+ if (isNaN(ngModel.$viewValue) && !(ngModel.$viewValue instanceof Array))
+ ngModel.$viewValue = 0;
+
+ if (ngModel.$viewValue == '')
+ ngModel.$viewValue = 0;
+
+ scope.value = ngModel.$viewValue;
+
+ // Do some sanity check of range values
+ if (options.range === true) {
+
+ // Check outer bounds for min and max values
+ if (angular.isDefined(options.min) && options.min > ngModel.$viewValue[0]) {
+ ngModel.$viewValue[0] = options.min;
+ }
+ if (angular.isDefined(options.max) && options.max < ngModel.$viewValue[1]) {
+ ngModel.$viewValue[1] = options.max;
+ }
+
+ // Check min and max range values
+ if (ngModel.$viewValue[0] >= ngModel.$viewValue[1]) {
+ // Min value should be less to equal to max value
+ if (prevRangeValues.min >= ngModel.$viewValue[1])
+ ngModel.$viewValue[0] = prevRangeValues.min;
+ // Max value should be less to equal to min value
+ if (prevRangeValues.max <= ngModel.$viewValue[0])
+ ngModel.$viewValue[1] = prevRangeValues.max;
+ }
+
+
+
+ // Store values for later user
+ prevRangeValues.min = ngModel.$viewValue[0];
+ prevRangeValues.max = ngModel.$viewValue[1];
+
+ }
+ elm.find(".slider").slider(method, ngModel.$viewValue);
+ elm.find(".ui-slider-handle").html("" + ngModel.$viewValue + "px")
+ };
+
+ scope.$watch("value", function () {
+ ngModel.$setViewValue(scope.value);
+ }, true);
+
+ scope.$watch(attrs.ngModel, function () {
+ if (options.range === true) {
+ ngModel.$render();
+ }
+ }, true);
+
+ function destroy() {
+ elm.find(".slider").slider('destroy');
+ }
+ elm.find(".slider").bind('$destroy', destroy);
+ };
+ }
+ };
+}]);
+
+
+
+/*********************************************************************************************************/
+/* spectrum color picker directive */
+/*********************************************************************************************************/
+
+angular.module('spectrumcolorpicker', [])
+ .directive('spectrum', function () {
+ return {
+ restrict: 'E',
+ transclude: true,
+ scope: {
+ colorselected: '=',
+ setColor: '=',
+ flat: '=',
+ showPalette: '='
+ },
+ link: function (scope, $element) {
+
+ var initColor;
+
+ $element.find("input").spectrum({
+ color: scope.colorselected,
+ allowEmpty: true,
+ preferredFormat: "hex",
+ showAlpha: true,
+ showInput: true,
+ flat: scope.flat,
+ localStorageKey: "spectrum.panel",
+ showPalette: scope.showPalette,
+ palette: [],
+ change: function (color) {
+
+ if (color) {
+ scope.colorselected = color.toRgbString();
+ }
+ else {
+ scope.colorselected = '';
+ }
+ scope.$apply();
+ },
+ move: function (color) {
+ scope.colorselected = color.toRgbString();
+ scope.$apply();
+ },
+ beforeShow: function (color) {
+ initColor = angular.copy(scope.colorselected);
+ $(this).spectrum("container").find(".sp-cancel").click(function (e) {
+ scope.colorselected = initColor;
+ scope.$apply();
+ });
+ },
+
+ });
+
+ scope.$watch('setcolor', function (setColor) {
+ if (scope.$eval(setColor) === true) {
+ $element.find("input").spectrum("set", scope.colorselected);
+ }
+ }, true);
+
+ },
+ template:
+ '
',
+ replace: true
+ };
+ })
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/dualSelectBox.js b/WebCms/Umbraco/Js/dualSelectBox.js
new file mode 100644
index 0000000..5409e66
--- /dev/null
+++ b/WebCms/Umbraco/Js/dualSelectBox.js
@@ -0,0 +1,48 @@
+
+function dualSelectBoxShift(id) {
+ var posVal = document.getElementById(id + "_posVals");
+ var selVal = document.getElementById(id + "_selVals");
+
+ // First check the possible items
+ for (var i=0;i 0) {
+ dashboard.height(clientHeight);
+ treeToggle.height(clientHeight);
+ }
+
+ treeToggle.show();
+ uiArea.show();
+ }
+
+}
+
+function resizeGuiWindow(windowName, newWidth, newHeight, window) {
+ //This no longer does anything and shouldn't be used.
+}
+
+function resizeGuiWindowWithTabs(windowName, newWidth, newHeight) {
+ right.document.all[windowName + "ContainerTable"].width = newWidth + 22
+ right.document.all[windowName + "ContainerTableSpacer"].width = newWidth
+ right.document.all[windowName + "Bottom"].width = newWidth + 12
+ right.document.all[windowName + "BottomSpacer"].width = newWidth
+ right.document.all[windowName].style.width = newWidth
+
+
+ // Der skal forskellig strrelse p hjden afhngig af om vinduet har en label i bunden
+ if (right.document.all[windowName + 'BottomLabel']) {
+ right.document.all[windowName + "ContainerTable"].height = newHeight - 13;
+ right.document.all[windowName].style.height = newHeight - 13;
+ } else {
+ right.document.all[windowName + "ContainerTable"].height = newHeight + 3;
+ right.document.all[windowName].style.height = newHeight + 3;
+ }
+}
diff --git a/WebCms/Umbraco/Js/init.js b/WebCms/Umbraco/Js/init.js
new file mode 100644
index 0000000..ded5ba0
--- /dev/null
+++ b/WebCms/Umbraco/Js/init.js
@@ -0,0 +1,70 @@
+/** Executed when the application starts, binds to events and set global state */
+app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache',
+ function (userService, $log, $rootScope, $location, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache) {
+
+ //This sets the default jquery ajax headers to include our csrf token, we
+ // need to user the beforeSend method because our token changes per user/login so
+ // it cannot be static
+ $.ajaxSetup({
+ beforeSend: function (xhr) {
+ xhr.setRequestHeader("X-XSRF-TOKEN", $cookies["XSRF-TOKEN"]);
+ }
+ });
+
+ /** Listens for authentication and checks if our required assets are loaded, if/once they are we'll broadcast a ready event */
+ eventsService.on("app.authenticated", function(evt, data) {
+ assetsService._loadInitAssets().then(function() {
+ appState.setGlobalState("isReady", true);
+
+ //send the ready event with the included returnToPath,returnToSearch data
+ eventsService.emit("app.ready", data);
+ returnToPath = null, returnToSearch = null;
+ });
+ });
+
+ /** execute code on each successful route */
+ $rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
+
+ if(current.params.section){
+ $rootScope.locationTitle = current.params.section + " - " + $location.$$host;
+ }
+ else {
+ $rootScope.locationTitle = "Umbraco - " + $location.$$host;
+ }
+
+ //reset the editorState on each successful route chage
+ editorState.reset();
+
+ //reset the file manager on each route change, the file collection is only relavent
+ // when working in an editor and submitting data to the server.
+ //This ensures that memory remains clear of any files and that the editors don't have to manually clear the files.
+ fileManager.clearFiles();
+ });
+
+ /** When the route change is rejected - based on checkAuth - we'll prevent the rejected route from executing including
+ wiring up it's controller, etc... and then redirect to the rejected URL. */
+ $rootScope.$on('$routeChangeError', function(event, current, previous, rejection) {
+ event.preventDefault();
+
+ var returnPath = null;
+ if (rejection.path == "/login" || rejection.path.startsWith("/login/")) {
+ //Set the current path before redirecting so we know where to redirect back to
+ returnPath = encodeURIComponent($location.url());
+ }
+
+ $location.path(rejection.path)
+ if (returnPath) {
+ $location.search("returnPath", returnPath);
+ }
+
+ });
+
+
+ /* this will initialize the navigation service once the application has started */
+ navigationService.init();
+
+ //check for touch device, add to global appState
+ //var touchDevice = ("ontouchstart" in window || window.touch || window.navigator.msMaxTouchPoints === 5 || window.DocumentTouch && document instanceof DocumentTouch);
+ var touchDevice = /android|webos|iphone|ipad|ipod|blackberry|iemobile|touch/i.test(navigator.userAgent.toLowerCase());
+ appState.setGlobalState("touchDevice", touchDevice);
+ }]);
diff --git a/WebCms/Umbraco/Js/install.loader.js b/WebCms/Umbraco/Js/install.loader.js
new file mode 100644
index 0000000..869521e
--- /dev/null
+++ b/WebCms/Umbraco/Js/install.loader.js
@@ -0,0 +1,17 @@
+LazyLoad.js( [
+ 'lib/jquery/jquery.min.js',
+ /* 1.1.5 */
+ 'lib/angular/1.1.5/angular.min.js',
+ 'lib/angular/1.1.5/angular-cookies.min.js',
+ 'lib/angular/1.1.5/angular-mobile.min.js',
+ 'lib/angular/1.1.5/angular-mocks.js',
+ 'lib/angular/1.1.5/angular-sanitize.min.js',
+ 'lib/underscore/underscore-min.js',
+ 'js/umbraco.installer.js',
+ 'js/umbraco.directives.js'
+ ], function () {
+ jQuery(document).ready(function () {
+ angular.bootstrap(document, ['ngSanitize', 'umbraco.install', 'umbraco.directives.validation']);
+ });
+ }
+);
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/language.aspx b/WebCms/Umbraco/Js/language.aspx
new file mode 100644
index 0000000..2649835
--- /dev/null
+++ b/WebCms/Umbraco/Js/language.aspx
@@ -0,0 +1 @@
+<%@ Page language="c#" Codebehind="language.aspx.cs" AutoEventWireup="True" Inherits="umbraco.js.language" %>
diff --git a/WebCms/Umbraco/Js/routes.js b/WebCms/Umbraco/Js/routes.js
new file mode 100644
index 0000000..c388ff5
--- /dev/null
+++ b/WebCms/Umbraco/Js/routes.js
@@ -0,0 +1,173 @@
+app.config(function ($routeProvider) {
+
+ /** This checks if the user is authenticated for a route and what the isRequired is set to.
+ Depending on whether isRequired = true, it first check if the user is authenticated and will resolve successfully
+ otherwise the route will fail and the $routeChangeError event will execute, in that handler we will redirect to the rejected
+ path that is resolved from this method and prevent default (prevent the route from executing) */
+ var canRoute = function(isRequired) {
+
+ return {
+ /** Checks that the user is authenticated, then ensures that are requires assets are loaded */
+ isAuthenticatedAndReady: function ($q, userService, $route, assetsService, appState) {
+ var deferred = $q.defer();
+
+ //don't need to check if we've redirected to login and we've already checked auth
+ if (!$route.current.params.section
+ && ($route.current.params.check === false || $route.current.params.check === "false")) {
+ deferred.resolve(true);
+ return deferred.promise;
+ }
+
+ userService.isAuthenticated()
+ .then(function () {
+
+ assetsService._loadInitAssets().then(function() {
+
+ //This could be the first time has loaded after the user has logged in, in this case
+ // we need to broadcast the authenticated event - this will be handled by the startup (init)
+ // handler to set/broadcast the ready state
+ var broadcast = appState.getGlobalState("isReady") !== true;
+
+ userService.getCurrentUser({ broadcastEvent: broadcast }).then(function (user) {
+ //is auth, check if we allow or reject
+ if (isRequired) {
+ // U4-5430, Benjamin Howarth
+ // We need to change the current route params if the user only has access to a single section
+ // To do this we need to grab the current user's allowed sections, then reject the promise with the correct path.
+ if (user.allowedSections.indexOf($route.current.params.section) > -1) {
+ //this will resolve successfully so the route will continue
+ deferred.resolve(true);
+ } else {
+ deferred.reject({ path: "/" + user.allowedSections[0] });
+ }
+ }
+ else {
+ deferred.reject({ path: "/" });
+ }
+ });
+
+ });
+
+ }, function () {
+ //not auth, check if we allow or reject
+ if (isRequired) {
+ //the check=false is checked above so that we don't have to make another http call to check
+ //if they are logged in since we already know they are not.
+ deferred.reject({ path: "/login/false" });
+ }
+ else {
+ //this will resolve successfully so the route will continue
+ deferred.resolve(true);
+ }
+ });
+ return deferred.promise;
+ }
+ };
+ };
+
+ /** When this is used to resolve it will attempt to log the current user out */
+ var doLogout = function() {
+ return {
+ isLoggedOut: function ($q, userService) {
+ var deferred = $q.defer();
+ userService.logout().then(function () {
+ //success so continue
+ deferred.resolve(true);
+ }, function() {
+ //logout failed somehow ? we'll reject with the login page i suppose
+ deferred.reject({ path: "/login/false" });
+ });
+ return deferred.promise;
+ }
+ }
+ }
+
+ $routeProvider
+ .when('/login', {
+ templateUrl: 'views/common/login.html',
+ //ensure auth is *not* required so it will redirect to /
+ resolve: canRoute(false)
+ })
+ .when('/login/:check', {
+ templateUrl: 'views/common/login.html',
+ //ensure auth is *not* required so it will redirect to /
+ resolve: canRoute(false)
+ })
+ .when('/logout', {
+ redirectTo: '/login/false',
+ resolve: doLogout()
+ })
+ .when('/:section', {
+ templateUrl: function (rp) {
+ if (rp.section.toLowerCase() === "default" || rp.section.toLowerCase() === "umbraco" || rp.section === "")
+ {
+ rp.section = "content";
+ }
+
+ rp.url = "dashboard.aspx?app=" + rp.section;
+ return 'views/common/dashboard.html';
+ },
+ resolve: canRoute(true)
+ })
+ .when('/:section/framed/:url', {
+ //This occurs when we need to launch some content in an iframe
+ templateUrl: function (rp) {
+ if (!rp.url)
+ throw "A framed resource must have a url route parameter";
+
+ return 'views/common/legacy.html';
+ },
+ resolve: canRoute(true)
+ })
+ .when('/:section/:tree/:method', {
+ templateUrl: function (rp) {
+ if (!rp.method)
+ return "views/common/dashboard.html";
+
+ //NOTE: This current isn't utilized by anything but does open up some cool opportunities for
+ // us since we'll be able to have specialized views for individual sections which is something
+ // we've never had before. So could utilize this for a new dashboard model when we get native
+ // angular dashboards working. Perhaps a normal section dashboard would list out the registered
+ // dashboards (as tabs if we wanted) and each tab could actually be a route link to one of these views?
+
+ return ('views/' + rp.tree + '/' + rp.method + '.html');
+ },
+ resolve: canRoute(true)
+ })
+ .when('/:section/:tree/:method/:id', {
+ //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method.
+ template: "",
+ //This controller will execute for this route, then we replace the template dynamnically based on the current tree.
+ controller: function ($scope, $route, $routeParams, treeService) {
+
+ if (!$routeParams.tree || !$routeParams.method) {
+ $scope.templateUrl = "views/common/dashboard.html";
+ }
+
+ // Here we need to figure out if this route is for a package tree and if so then we need
+ // to change it's convention view path to:
+ // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html
+
+ // otherwise if it is a core tree we use the core paths:
+ // views/{treetype}/{method}.html
+
+ var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree);
+
+ if (packageTreeFolder) {
+ $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath +
+ "/" + packageTreeFolder +
+ "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html");
+ }
+ else {
+ $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html');
+ }
+
+ },
+ resolve: canRoute(true)
+ })
+ .otherwise({ redirectTo: '/login' });
+ }).config(function ($locationProvider) {
+
+ //$locationProvider.html5Mode(false).hashPrefix('!'); //turn html5 mode off
+ // $locationProvider.html5Mode(true); //turn html5 mode on
+ });
diff --git a/WebCms/Umbraco/Js/umbraco.controllers.js b/WebCms/Umbraco/Js/umbraco.controllers.js
new file mode 100644
index 0000000..e2ec4de
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.controllers.js
@@ -0,0 +1,16053 @@
+/*! umbraco
+ * https://github.com/umbraco/umbraco-cms/
+ * Copyright (c) 2016 Umbraco HQ;
+ * Licensed
+ */
+
+(function() {
+
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.MainController
+ * @function
+ *
+ * @description
+ * The main application controller
+ *
+ */
+function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale) {
+
+ //the null is important because we do an explicit bool check on this in the view
+ //the avatar is by default the umbraco logo
+ $scope.authenticated = null;
+ $scope.avatar = [
+ { value: "assets/img/application/logo.png" },
+ { value: "assets/img/application/logo@2x.png" },
+ { value: "assets/img/application/logo@3x.png" }
+ ];
+ $scope.touchDevice = appState.getGlobalState("touchDevice");
+
+
+ $scope.removeNotification = function (index) {
+ notificationsService.remove(index);
+ };
+
+ $scope.closeDialogs = function (event) {
+ //only close dialogs if non-link and non-buttons are clicked
+ var el = event.target.nodeName;
+ var els = ["INPUT", "A", "BUTTON"];
+
+ if (els.indexOf(el) >= 0) { return; }
+
+ var parents = $(event.target).parents("a,button");
+ if (parents.length > 0) {
+ return;
+ }
+
+ //SD: I've updated this so that we don't close the dialog when clicking inside of the dialog
+ var nav = $(event.target).parents("#dialog");
+ if (nav.length === 1) {
+ return;
+ }
+
+ eventsService.emit("app.closeDialogs", event);
+ };
+
+ var evts = [];
+
+ //when a user logs out or timesout
+ evts.push(eventsService.on("app.notAuthenticated", function () {
+ $scope.authenticated = null;
+ $scope.user = null;
+ }));
+
+ //when the app is read/user is logged in, setup the data
+ evts.push(eventsService.on("app.ready", function (evt, data) {
+
+ $scope.authenticated = data.authenticated;
+ $scope.user = data.user;
+
+ updateChecker.check().then(function(update) {
+ if (update && update !== "null") {
+ if (update.type !== "None") {
+ var notification = {
+ headline: "Update available",
+ message: "Click to download",
+ sticky: true,
+ type: "info",
+ url: update.url
+ };
+ notificationsService.add(notification);
+ }
+ }
+ });
+
+ //if the user has changed we need to redirect to the root so they don't try to continue editing the
+ //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true)
+ if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) {
+ $location.path("/").search("");
+ historyService.removeAll();
+ treeService.clearCache();
+ }
+
+ //Load locale file
+ if ($scope.user.locale) {
+ tmhDynamicLocale.set($scope.user.locale);
+ }
+
+ if ($scope.user.emailHash) {
+
+ //let's attempt to load the avatar, it might not exist or we might not have
+ // internet access, well get an empty string back
+ $http.get(umbRequestHelper.getApiUrl("gravatarApiBaseUrl", "GetCurrentUserGravatarUrl"))
+ .then(
+ function successCallback(response) {
+ // if we can't download the gravatar for some reason, an null gets returned, we cannot do anything
+ if (response.data !== "null") {
+ if ($scope.user && $scope.user.emailHash) {
+ var avatarBaseUrl = "https://www.gravatar.com/avatar/";
+ var hash = $scope.user.emailHash;
+
+ $scope.avatar = [
+ { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" },
+ { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" },
+ { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" }
+ ];
+ }
+ }
+
+ }, function errorCallback(response) {
+ //cannot load it from the server so we cannot do anything
+ });
+ }
+ }));
+
+ evts.push(eventsService.on("app.ysod", function (name, error) {
+ $scope.ysodOverlay = {
+ view: "ysod",
+ error: error,
+ show: true
+ };
+ }));
+
+ //ensure to unregister from all events!
+ $scope.$on('$destroy', function () {
+ for (var e in evts) {
+ eventsService.unsubscribe(evts[e]);
+ }
+ });
+
+}
+
+
+//register it
+angular.module('umbraco').controller("Umbraco.MainController", MainController).
+ config(function (tmhDynamicLocaleProvider) {
+ //Set url for locale files
+ tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js');
+ });
+
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.NavigationController
+ * @function
+ *
+ * @description
+ * Handles the section area of the app
+ *
+ * @param {navigationService} navigationService A reference to the navigationService
+ */
+function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper) {
+
+ //TODO: Need to think about this and an nicer way to acheive what this is doing.
+ //the tree event handler i used to subscribe to the main tree click events
+ $scope.treeEventHandler = $({});
+ navigationService.setupTreeEvents($scope.treeEventHandler);
+
+ //Put the navigation service on this scope so we can use it's methods/properties in the view.
+ // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since
+ // when we create a dialog we pass in this scope to be used for the dialog's scope instead of creating a new one.
+ $scope.nav = navigationService;
+ // TODO: Lets fix this, it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope,
+ // this is going to lead to problems/confusion. I really don't think passing scope's around is very good practice.
+ $rootScope.nav = navigationService;
+
+ //set up our scope vars
+ $scope.showContextMenuDialog = false;
+ $scope.showContextMenu = false;
+ $scope.showSearchResults = false;
+ $scope.menuDialogTitle = null;
+ $scope.menuActions = [];
+ $scope.menuNode = null;
+
+ $scope.currentSection = appState.getSectionState("currentSection");
+ $scope.showNavigation = appState.getGlobalState("showNavigation");
+
+ //trigger search with a hotkey:
+ keyboardService.bind("ctrl+shift+s", function () {
+ navigationService.showSearch();
+ });
+
+ //trigger dialods with a hotkey:
+ keyboardService.bind("esc", function () {
+ eventsService.emit("app.closeDialogs");
+ });
+
+ $scope.selectedId = navigationService.currentId;
+
+ var evts = [];
+
+ //Listen for global state changes
+ evts.push(eventsService.on("appState.globalState.changed", function(e, args) {
+ if (args.key === "showNavigation") {
+ $scope.showNavigation = args.value;
+ }
+ }));
+
+ //Listen for menu state changes
+ evts.push(eventsService.on("appState.menuState.changed", function(e, args) {
+ if (args.key === "showMenuDialog") {
+ $scope.showContextMenuDialog = args.value;
+ }
+ if (args.key === "showMenu") {
+ $scope.showContextMenu = args.value;
+ }
+ if (args.key === "dialogTitle") {
+ $scope.menuDialogTitle = args.value;
+ }
+ if (args.key === "menuActions") {
+ $scope.menuActions = args.value;
+ }
+ if (args.key === "currentNode") {
+ $scope.menuNode = args.value;
+ }
+ }));
+
+ //Listen for section state changes
+ evts.push(eventsService.on("appState.treeState.changed", function(e, args) {
+ var f = args;
+ if (args.value.root && args.value.root.metaData.containsTrees === false) {
+ $rootScope.emptySection = true;
+ }
+ else {
+ $rootScope.emptySection = false;
+ }
+ }));
+
+ //Listen for section state changes
+ evts.push(eventsService.on("appState.sectionState.changed", function(e, args) {
+ //section changed
+ if (args.key === "currentSection") {
+ $scope.currentSection = args.value;
+ }
+ //show/hide search results
+ if (args.key === "showSearchResults") {
+ $scope.showSearchResults = args.value;
+ }
+ }));
+
+ //This reacts to clicks passed to the body element which emits a global call to close all dialogs
+ evts.push(eventsService.on("app.closeDialogs", function(event) {
+ if (appState.getGlobalState("stickyNavigation")) {
+ navigationService.hideNavigation();
+ //TODO: don't know why we need this? - we are inside of an angular event listener.
+ angularHelper.safeApply($scope);
+ }
+ }));
+
+ //when a user logs out or timesout
+ evts.push(eventsService.on("app.notAuthenticated", function() {
+ $scope.authenticated = false;
+ }));
+
+ //when the application is ready and the user is authorized setup the data
+ evts.push(eventsService.on("app.ready", function(evt, data) {
+ $scope.authenticated = true;
+ }));
+
+ //this reacts to the options item in the tree
+ //todo, migrate to nav service
+ $scope.searchShowMenu = function (ev, args) {
+ //always skip default
+ args.skipDefault = true;
+ navigationService.showMenu(ev, args);
+ };
+
+ //todo, migrate to nav service
+ $scope.searchHide = function () {
+ navigationService.hideSearch();
+ };
+
+ //the below assists with hiding/showing the tree
+ var treeActive = false;
+
+ //Sets a service variable as soon as the user hovers the navigation with the mouse
+ //used by the leaveTree method to delay hiding
+ $scope.enterTree = function (event) {
+ treeActive = true;
+ };
+
+ // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again
+ $scope.leaveTree = function(event) {
+ //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down
+ if (!event) {
+ return;
+ }
+ if (!appState.getGlobalState("touchDevice")) {
+ treeActive = false;
+ $timeout(function() {
+ if (!treeActive) {
+ navigationService.hideTree();
+ }
+ }, 300);
+ }
+ };
+
+ //ensure to unregister from all events!
+ $scope.$on('$destroy', function () {
+ for (var e in evts) {
+ eventsService.unsubscribe(evts[e]);
+ }
+ });
+}
+
+//register it
+angular.module('umbraco').controller("Umbraco.NavigationController", NavigationController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.SearchController
+ * @function
+ *
+ * @description
+ * Controls the search functionality in the site
+ *
+ */
+function SearchController($scope, searchService, $log, $location, navigationService, $q) {
+
+ $scope.searchTerm = null;
+ $scope.searchResults = [];
+ $scope.isSearching = false;
+ $scope.selectedResult = -1;
+
+
+ $scope.navigateResults = function(ev){
+ //38: up 40: down, 13: enter
+
+ switch(ev.keyCode){
+ case 38:
+ iterateResults(true);
+ break;
+ case 40:
+ iterateResults(false);
+ break;
+ case 13:
+ if ($scope.selectedItem) {
+ $location.path($scope.selectedItem.editorPath);
+ navigationService.hideSearch();
+ }
+ break;
+ }
+ };
+
+
+ var group = undefined;
+ var groupIndex = -1;
+ var itemIndex = -1;
+ $scope.selectedItem = undefined;
+
+
+ function iterateResults(up){
+ //default group
+ if(!group){
+ group = $scope.groups[0];
+ groupIndex = 0;
+ }
+
+ if(up){
+ if(itemIndex === 0){
+ if(groupIndex === 0){
+ gotoGroup($scope.groups.length-1, true);
+ }else{
+ gotoGroup(groupIndex-1, true);
+ }
+ }else{
+ gotoItem(itemIndex-1);
+ }
+ }else{
+ if(itemIndex < group.results.length-1){
+ gotoItem(itemIndex+1);
+ }else{
+ if(groupIndex === $scope.groups.length-1){
+ gotoGroup(0);
+ }else{
+ gotoGroup(groupIndex+1);
+ }
+ }
+ }
+ }
+
+ function gotoGroup(index, up){
+ groupIndex = index;
+ group = $scope.groups[groupIndex];
+
+ if(up){
+ gotoItem(group.results.length-1);
+ }else{
+ gotoItem(0);
+ }
+ }
+
+ function gotoItem(index){
+ itemIndex = index;
+ $scope.selectedItem = group.results[itemIndex];
+ }
+
+ //used to cancel any request in progress if another one needs to take it's place
+ var canceler = null;
+
+ $scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) {
+ $scope.$apply(function() {
+ if ($scope.searchTerm) {
+ if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
+ $scope.isSearching = true;
+ navigationService.showSearch();
+ $scope.selectedItem = undefined;
+
+ //a canceler exists, so perform the cancelation operation and reset
+ if (canceler) {
+ canceler.resolve();
+ canceler = $q.defer();
+ }
+ else {
+ canceler = $q.defer();
+ }
+
+ searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function(result) {
+ $scope.groups = _.filter(result, function (group) { return group.results.length > 0; });
+ //set back to null so it can be re-created
+ canceler = null;
+ });
+ }
+ }
+ else {
+ $scope.isSearching = false;
+ navigationService.hideSearch();
+ $scope.selectedItem = undefined;
+ }
+ });
+ }, 200));
+
+}
+//register it
+angular.module('umbraco').controller("Umbraco.SearchController", SearchController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.MainController
+ * @function
+ *
+ * @description
+ * The controller for the AuthorizeUpgrade login page
+ *
+ */
+function AuthorizeUpgradeController($scope, $window) {
+
+ //Add this method to the scope - this method will be called by the login dialog controller when the login is successful
+ // then we'll handle the redirect.
+ $scope.submit = function (event) {
+
+ var qry = $window.location.search.trimStart("?").split("&");
+ var redir = _.find(qry, function(item) {
+ return item.startsWith("redir=");
+ });
+ if (redir) {
+ $window.location = decodeURIComponent(redir.split("=")[1]);
+ }
+ else {
+ $window.location = "/";
+ }
+
+ };
+
+}
+
+angular.module('umbraco').controller("Umbraco.AuthorizeUpgradeController", AuthorizeUpgradeController);
+/**
+ * @ngdoc controller
+ * @name Umbraco.DashboardController
+ * @function
+ *
+ * @description
+ * Controls the dashboards of the application
+ *
+ */
+
+function DashboardController($scope, $routeParams, dashboardResource, localizationService) {
+
+ $scope.page = {};
+ $scope.page.nameLocked = true;
+ $scope.page.loading = true;
+
+ $scope.dashboard = {};
+ localizationService.localize("sections_" + $routeParams.section).then(function(name){
+ $scope.dashboard.name = name;
+ });
+
+ dashboardResource.getDashboard($routeParams.section).then(function(tabs){
+ $scope.dashboard.tabs = tabs;
+ $scope.page.loading = false;
+ });
+}
+
+
+//register it
+angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController);
+
+angular.module("umbraco")
+ .controller("Umbraco.Dialogs.ApprovedColorPickerController", function ($scope, $http, umbPropEditorHelper, assetsService) {
+ assetsService.loadJs("lib/cssparser/cssparser.js")
+ .then(function () {
+
+ var cssPath = $scope.dialogData.cssPath;
+ $scope.cssClass = $scope.dialogData.cssClass;
+
+ $scope.classes = [];
+
+ $scope.change = function (newClass) {
+ $scope.model.value = newClass;
+ }
+
+ $http.get(cssPath)
+ .success(function (data) {
+ var parser = new CSSParser();
+ $scope.classes = parser.parse(data, false, false).cssRules;
+ $scope.classes.splice(0, 0, "noclass");
+ })
+
+ assetsService.loadCss("/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css");
+ assetsService.loadCss(cssPath);
+ });
+});
+function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) {
+
+ $scope.defaultButton = null;
+ $scope.subButtons = [];
+ var dialogOptions = $scope.$parent.dialogOptions;
+
+ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
+ function performSave(args) {
+ contentEditingHelper.contentEditorPerformSave({
+ statusMessage: args.statusMessage,
+ saveMethod: args.saveMethod,
+ scope: $scope,
+ content: $scope.content
+ }).then(function (content) {
+ //success
+ if (dialogOptions.closeOnSave) {
+ $scope.submit(content);
+ }
+
+ }, function(err) {
+ //error
+ });
+ }
+
+ function filterTabs(entity, blackList) {
+ if (blackList) {
+ _.each(entity.tabs, function (tab) {
+ tab.hide = _.contains(blackList, tab.alias);
+ });
+ }
+
+ return entity;
+ };
+
+ function init(content) {
+ var buttons = contentEditingHelper.configureContentEditorButtons({
+ create: $routeParams.create,
+ content: content,
+ methods: {
+ saveAndPublish: $scope.saveAndPublish,
+ sendToPublish: $scope.sendToPublish,
+ save: $scope.save,
+ unPublish: angular.noop
+ }
+ });
+ $scope.defaultButton = buttons.defaultButton;
+ $scope.subButtons = buttons.subButtons;
+
+ //This is a total hack but we have really no other way of sharing data to the property editors of this
+ // content item, so we'll just set the property on the content item directly
+ $scope.content.isDialogEditor = true;
+
+ editorState.set($scope.content);
+ }
+
+ //check if the entity is being passed in, otherwise load it from the server
+ if (angular.isObject(dialogOptions.entity)) {
+ $scope.loaded = true;
+ $scope.content = filterTabs(dialogOptions.entity, dialogOptions.tabFilter);
+ init($scope.content);
+ }
+ else {
+ contentResource.getById(dialogOptions.id)
+ .then(function(data) {
+ $scope.loaded = true;
+ $scope.content = filterTabs(data, dialogOptions.tabFilter);
+ init($scope.content);
+ //in one particular special case, after we've created a new item we redirect back to the edit
+ // route but there might be server validation errors in the collection which we need to display
+ // after the redirect, so we will bind all subscriptions which will show the server validation errors
+ // if there are any and then clear them so the collection no longer persists them.
+ serverValidationManager.executeAndClearAllSubscriptions();
+ });
+ }
+
+ $scope.sendToPublish = function () {
+ performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." });
+ };
+
+ $scope.saveAndPublish = function () {
+ performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." });
+ };
+
+ $scope.save = function () {
+ performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." });
+ };
+
+ // this method is called for all action buttons and then we proxy based on the btn definition
+ $scope.performAction = function (btn) {
+
+ if (!btn || !angular.isFunction(btn.handler)) {
+ throw "btn.handler must be a function reference";
+ }
+
+ if (!$scope.busy) {
+ btn.handler.apply(this);
+ }
+ };
+
+}
+
+
+angular.module("umbraco")
+ .controller("Umbraco.Dialogs.Content.EditController", ContentEditDialogController);
+angular.module("umbraco")
+ .controller("Umbraco.Dialogs.HelpController", function ($scope, $location, $routeParams, helpService, userService, localizationService) {
+ $scope.section = $routeParams.section;
+ $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
+
+ if(!$scope.section){
+ $scope.section = "content";
+ }
+
+ $scope.sectionName = $scope.section;
+
+ var rq = {};
+ rq.section = $scope.section;
+
+ //translate section name
+ localizationService.localize("sections_" + rq.section).then(function (value) {
+ $scope.sectionName = value;
+ });
+
+ userService.getCurrentUser().then(function(user){
+
+ rq.usertype = user.userType;
+ rq.lang = user.locale;
+
+ if($routeParams.url){
+ rq.path = decodeURIComponent($routeParams.url);
+
+ if(rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0){
+ rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
+ }
+
+ if(rq.path.indexOf(".aspx") > 0){
+ rq.path = rq.path.substring(0, rq.path.indexOf(".aspx"));
+ }
+
+ }else{
+ rq.path = rq.section + "/" + $routeParams.tree + "/" + $routeParams.method;
+ }
+
+ helpService.findHelp(rq).then(function(topics){
+ $scope.topics = topics;
+ });
+
+ helpService.findVideos(rq).then(function(videos){
+ $scope.videos = videos;
+ });
+
+ });
+
+
+ });
+//used for the icon picker dialog
+angular.module("umbraco")
+ .controller("Umbraco.Dialogs.IconPickerController",
+ function ($scope, iconHelper) {
+
+ iconHelper.getIcons().then(function(icons){
+ $scope.icons = icons;
+ });
+
+ $scope.submitClass = function (icon) {
+ if($scope.color) {
+ $scope.submit(icon + " " + $scope.color);
+ }
+ else {
+ $scope.submit(icon);
+ }
+ };
+
+ }
+ );
+/**
+ * @ngdoc controller
+ * @name Umbraco.Dialogs.InsertMacroController
+ * @function
+ *
+ * @description
+ * The controller for the custom insert macro dialog. Until we upgrade the template editor to be angular this
+ * is actually loaded into an iframe with full html.
+ */
+function InsertMacroController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper) {
+
+ /** changes the view to edit the params of the selected macro */
+ function editParams() {
+ //get the macro params if there are any
+ macroResource.getMacroParameters($scope.selectedMacro.id)
+ .then(function (data) {
+
+ //go to next page if there are params otherwise we can just exit
+ if (!angular.isArray(data) || data.length === 0) {
+ //we can just exist!
+ submitForm();
+
+ } else {
+ $scope.wizardStep = "paramSelect";
+ $scope.macroParams = data;
+
+ //fill in the data if we are editing this macro
+ if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroParamsDictionary) {
+ _.each($scope.dialogData.macroData.macroParamsDictionary, function (val, key) {
+ var prop = _.find($scope.macroParams, function (item) {
+ return item.alias == key;
+ });
+ if (prop) {
+
+ if (_.isString(val)) {
+ //we need to unescape values as they have most likely been escaped while inserted
+ val = _.unescape(val);
+
+ //detect if it is a json string
+ if (val.detectIsJson()) {
+ try {
+ //Parse it to json
+ prop.value = angular.fromJson(val);
+ }
+ catch (e) {
+ // not json
+ prop.value = val;
+ }
+ }
+ else {
+ prop.value = val;
+ }
+ }
+ else {
+ prop.value = val;
+ }
+ }
+ });
+
+ }
+ }
+ });
+ }
+
+ /** submit the filled out macro params */
+ function submitForm() {
+
+ //collect the value data, close the dialog and send the data back to the caller
+
+ //create a dictionary for the macro params
+ var paramDictionary = {};
+ _.each($scope.macroParams, function (item) {
+
+ var val = item.value;
+
+ if (item.value != null && item.value != undefined && !_.isString(item.value)) {
+ try {
+ val = angular.toJson(val);
+ }
+ catch (e) {
+ // not json
+ }
+ }
+
+ //each value needs to be xml escaped!! since the value get's stored as an xml attribute
+ paramDictionary[item.alias] = _.escape(val);
+
+ });
+
+ //need to find the macro alias for the selected id
+ var macroAlias = $scope.selectedMacro.alias;
+
+ //get the syntax based on the rendering engine
+ var syntax;
+ if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "WebForms") {
+ syntax = macroService.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
+ }
+ else if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "Mvc") {
+ syntax = macroService.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
+ }
+ else {
+ syntax = macroService.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
+ }
+
+ $scope.submit({ syntax: syntax, macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
+ }
+
+ $scope.macros = [];
+ $scope.selectedMacro = null;
+ $scope.wizardStep = "macroSelect";
+ $scope.macroParams = [];
+
+ $scope.submitForm = function () {
+
+ if (formHelper.submitForm({ scope: $scope })) {
+
+ formHelper.resetForm({ scope: $scope });
+
+ if ($scope.wizardStep === "macroSelect") {
+ editParams();
+ }
+ else {
+ submitForm();
+ }
+
+ }
+ };
+
+ //here we check to see if we've been passed a selected macro and if so we'll set the
+ //editor to start with parameter editing
+ if ($scope.dialogData && $scope.dialogData.macroData) {
+ $scope.wizardStep = "paramSelect";
+ }
+
+ //get the macro list - pass in a filter if it is only for rte
+ entityResource.getAll("Macro", ($scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true) ? "UseInEditor=true" : null)
+ .then(function (data) {
+
+ //if 'allowedMacros' is specified, we need to filter
+ if (angular.isArray($scope.dialogData.allowedMacros) && $scope.dialogData.allowedMacros.length > 0) {
+ $scope.macros = _.filter(data, function(d) {
+ return _.contains($scope.dialogData.allowedMacros, d.alias);
+ });
+ }
+ else {
+ $scope.macros = data;
+ }
+
+
+ //check if there's a pre-selected macro and if it exists
+ if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroAlias) {
+ var found = _.find(data, function (item) {
+ return item.alias === $scope.dialogData.macroData.macroAlias;
+ });
+ if (found) {
+ //select the macro and go to next screen
+ $scope.selectedMacro = found;
+ editParams();
+ return;
+ }
+ }
+ //we don't have a pre-selected macro so ensure the correct step is set
+ $scope.wizardStep = "macroSelect";
+ });
+
+
+}
+
+angular.module("umbraco").controller("Umbraco.Dialogs.InsertMacroController", InsertMacroController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Dialogs.LegacyDeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function LegacyDeleteController($scope, legacyResource, treeService, navigationService) {
+
+ $scope.performDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+
+ legacyResource.deleteItem({
+ nodeId: $scope.currentNode.id,
+ nodeType: $scope.currentNode.nodeType,
+ alias: $scope.currentNode.name,
+ }).then(function () {
+ $scope.currentNode.loading = false;
+ //TODO: Need to sync tree, etc...
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Dialogs.LegacyDeleteController", LegacyDeleteController);
+
+//used for the media picker dialog
+angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
+ function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
+ var dialogOptions = $scope.dialogOptions;
+
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ $scope.dialogTreeEventHandler = $({});
+ $scope.target = {};
+ $scope.searchInfo = {
+ searchFromId: null,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ }
+
+ if (dialogOptions.currentTarget) {
+ $scope.target = dialogOptions.currentTarget;
+
+ //if we have a node ID, we fetch the current node to build the form data
+ if ($scope.target.id) {
+
+ if (!$scope.target.path) {
+ entityResource.getPath($scope.target.id, "Document").then(function (path) {
+ $scope.target.path = path;
+ //now sync the tree to this path
+ $scope.dialogTreeEventHandler.syncTree({ path: $scope.target.path, tree: "content" });
+ });
+ }
+
+ contentResource.getNiceUrl($scope.target.id).then(function (url) {
+ $scope.target.url = url;
+ });
+ }
+ }
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.listViewNode) {
+ //check if list view 'search' node was selected
+
+ $scope.searchInfo.showSearch = true;
+ $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
+ $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
+ }
+ else {
+ eventsService.emit("dialogs.linkPicker.select", args);
+
+ if ($scope.currentNode) {
+ //un-select if there's a current one selected
+ $scope.currentNode.selected = false;
+ }
+
+ $scope.currentNode = args.node;
+ $scope.currentNode.selected = true;
+ $scope.target.id = args.node.id;
+ $scope.target.name = args.node.name;
+
+ if (args.node.id < 0) {
+ $scope.target.url = "/";
+ }
+ else {
+ contentResource.getNiceUrl(args.node.id).then(function (url) {
+ $scope.target.url = url;
+ });
+ }
+
+ if (!angular.isUndefined($scope.target.isMedia)) {
+ delete $scope.target.isMedia;
+ }
+ }
+ }
+
+ function nodeExpandedHandler(ev, args) {
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+ //check if any of the items are list views, if so we need to add a custom
+ // child: A node to activate the search
+ if (child.metaData.isContainer) {
+ child.hasChildren = true;
+ child.children = [
+ {
+ level: child.level + 1,
+ hasChildren: false,
+ name: searchText,
+ metaData: {
+ listViewNode: child,
+ },
+ cssClass: "icon umb-tree-icon sprTree icon-search",
+ cssClasses: ["not-published"]
+ }
+ ];
+ }
+ });
+ }
+ }
+
+ $scope.switchToMediaPicker = function () {
+ userService.getCurrentUser().then(function (userData) {
+ dialogService.mediaPicker({
+ startNodeId: userData.startMediaId,
+ callback: function (media) {
+ $scope.target.id = media.id;
+ $scope.target.isMedia = true;
+ $scope.target.name = media.name;
+ $scope.target.url = mediaHelper.resolveFile(media);
+ }
+ });
+ });
+ };
+
+ $scope.hideSearch = function () {
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = null;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
+
+ // method to select a search result
+ $scope.selectResult = function (evt, result) {
+ result.selected = result.selected === true ? false : true;
+ nodeSelectHandler(evt, {event: evt, node: result});
+ };
+
+ //callback when there are search results
+ $scope.onSearchResults = function (results) {
+ $scope.searchInfo.results = results;
+ $scope.searchInfo.showSearch = true;
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ });
+ });
+angular.module("umbraco").controller("Umbraco.Dialogs.LoginController",
+ function ($scope, $cookies, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource) {
+
+ var setFieldFocus = function(form, field) {
+ $timeout(function() {
+ $("form[name='" + form + "'] input[name='" + field + "']").focus();
+ });
+ }
+
+ function resetInputValidation() {
+ $scope.confirmPassword = "";
+ $scope.password = "";
+ $scope.login = "";
+ if ($scope.loginForm) {
+ $scope.loginForm.username.$setValidity('auth', true);
+ $scope.loginForm.password.$setValidity('auth', true);
+ }
+ if ($scope.requestPasswordResetForm) {
+ $scope.requestPasswordResetForm.email.$setValidity("auth", true);
+ }
+ if ($scope.setPasswordForm) {
+ $scope.setPasswordForm.password.$setValidity('auth', true);
+ $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
+ }
+ }
+
+ $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset;
+
+ $scope.showLogin = function () {
+ $scope.errorMsg = "";
+ resetInputValidation();
+ $scope.view = "login";
+ setFieldFocus("loginForm", "username");
+ }
+
+ $scope.showRequestPasswordReset = function () {
+ $scope.errorMsg = "";
+ resetInputValidation();
+ $scope.view = "request-password-reset";
+ $scope.showEmailResetConfirmation = false;
+ setFieldFocus("requestPasswordResetForm", "email");
+ }
+
+ $scope.showSetPassword = function () {
+ $scope.errorMsg = "";
+ resetInputValidation();
+ $scope.view = "set-password";
+ setFieldFocus("setPasswordForm", "password");
+ }
+
+ var d = new Date();
+ var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday");
+ var konamiMode = $cookies.konamiLogin;
+ if (konamiMode == "1") {
+ $scope.greeting = "Happy " + konamiGreetings[d.getDay()];
+ } else {
+ localizationService.localize("login_greeting" + d.getDay()).then(function (label) {
+ $scope.greeting = label;
+ }); // weekday[d.getDay()];
+ }
+ $scope.errorMsg = "";
+
+ $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl;
+ $scope.externalLoginProviders = externalLoginInfo.providers;
+ $scope.externalLoginInfo = externalLoginInfo;
+ $scope.resetPasswordCodeInfo = resetPasswordCodeInfo;
+
+ $scope.activateKonamiMode = function () {
+ if ($cookies.konamiLogin == "1") {
+ // somehow I can't update the cookie value using $cookies, so going native
+ document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;";
+ document.location.reload();
+ } else {
+ document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;";
+ $scope.$apply(function () {
+ $scope.greeting = "Happy " + konamiGreetings[d.getDay()];
+ });
+ }
+ }
+
+ $scope.loginSubmit = function (login, password) {
+
+ //if the login and password are not empty we need to automatically
+ // validate them - this is because if there are validation errors on the server
+ // then the user has to change both username & password to resubmit which isn't ideal,
+ // so if they're not empty, we'll just make sure to set them to valid.
+ if (login && password && login.length > 0 && password.length > 0) {
+ $scope.loginForm.username.$setValidity('auth', true);
+ $scope.loginForm.password.$setValidity('auth', true);
+ }
+
+ if ($scope.loginForm.$invalid) {
+ return;
+ }
+
+ userService.authenticate(login, password)
+ .then(function (data) {
+ $scope.submit(true);
+ }, function (reason) {
+ $scope.errorMsg = reason.errorMsg;
+
+ //set the form inputs to invalid
+ $scope.loginForm.username.$setValidity("auth", false);
+ $scope.loginForm.password.$setValidity("auth", false);
+ });
+
+ //setup a watch for both of the model values changing, if they change
+ // while the form is invalid, then revalidate them so that the form can
+ // be submitted again.
+ $scope.loginForm.username.$viewChangeListeners.push(function () {
+ if ($scope.loginForm.username.$invalid) {
+ $scope.loginForm.username.$setValidity('auth', true);
+ }
+ });
+ $scope.loginForm.password.$viewChangeListeners.push(function () {
+ if ($scope.loginForm.password.$invalid) {
+ $scope.loginForm.password.$setValidity('auth', true);
+ }
+ });
+ };
+
+ $scope.requestPasswordResetSubmit = function (email) {
+
+ if (email && email.length > 0) {
+ $scope.requestPasswordResetForm.email.$setValidity('auth', true);
+ }
+
+ $scope.showEmailResetConfirmation = false;
+
+ if ($scope.requestPasswordResetForm.$invalid) {
+ return;
+ }
+
+ $scope.errorMsg = "";
+
+ authResource.performRequestPasswordReset(email)
+ .then(function () {
+ //remove the email entered
+ $scope.email = "";
+ $scope.showEmailResetConfirmation = true;
+ }, function (reason) {
+ $scope.errorMsg = reason.errorMsg;
+ $scope.requestPasswordResetForm.email.$setValidity("auth", false);
+ });
+
+ $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () {
+ if ($scope.requestPasswordResetForm.email.$invalid) {
+ $scope.requestPasswordResetForm.email.$setValidity('auth', true);
+ }
+ });
+ };
+
+ $scope.setPasswordSubmit = function (password, confirmPassword) {
+
+ $scope.showSetPasswordConfirmation = false;
+
+ if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) {
+ $scope.setPasswordForm.password.$setValidity('auth', true);
+ $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
+ }
+
+ if ($scope.setPasswordForm.$invalid) {
+ return;
+ }
+
+ authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode)
+ .then(function () {
+ $scope.showSetPasswordConfirmation = true;
+ $scope.resetComplete = true;
+
+ //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again
+ resetPasswordCodeInfo.resetCodeModel = null;
+
+ }, function (reason) {
+ if (reason.data && reason.data.Message) {
+ $scope.errorMsg = reason.data.Message;
+ }
+ else {
+ $scope.errorMsg = reason.errorMsg;
+ }
+ $scope.setPasswordForm.password.$setValidity("auth", false);
+ $scope.setPasswordForm.confirmPassword.$setValidity("auth", false);
+ });
+
+ $scope.setPasswordForm.password.$viewChangeListeners.push(function () {
+ if ($scope.setPasswordForm.password.$invalid) {
+ $scope.setPasswordForm.password.$setValidity('auth', true);
+ }
+ });
+ $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () {
+ if ($scope.setPasswordForm.confirmPassword.$invalid) {
+ $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
+ }
+ });
+ }
+
+
+ //Now, show the correct panel:
+
+ if ($scope.resetPasswordCodeInfo.resetCodeModel) {
+ $scope.showSetPassword();
+ }
+ else if ($scope.resetPasswordCodeInfo.errors.length > 0) {
+ $scope.view = "password-reset-code-expired";
+ }
+ else {
+ $scope.showLogin();
+ }
+
+ });
+
+//used for the macro picker dialog
+angular.module("umbraco").controller("Umbraco.Dialogs.MacroPickerController", function ($scope, macroFactory, umbPropEditorHelper) {
+ $scope.macros = macroFactory.all(true);
+ $scope.dialogMode = "list";
+
+ $scope.configureMacro = function(macro){
+ $scope.dialogMode = "configure";
+ $scope.dialogData.macro = macroFactory.getMacro(macro.alias);
+ //set the correct view for each item
+ for (var i = 0; i < dialogData.macro.properties.length; i++) {
+ dialogData.macro.properties[i].editorView = umbPropEditorHelper.getViewPath(dialogData.macro.properties[i].view);
+ }
+ };
+});
+//used for the media picker dialog
+angular.module("umbraco")
+ .controller("Umbraco.Dialogs.MediaPickerController",
+ function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, eventsService, treeService, $cookies, $element, $timeout) {
+
+ var dialogOptions = $scope.dialogOptions;
+
+ $scope.onlyImages = dialogOptions.onlyImages;
+ $scope.showDetails = dialogOptions.showDetails;
+ $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false;
+ $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
+ $scope.cropSize = dialogOptions.cropSize;
+
+
+ //preload selected item
+ $scope.target = undefined;
+ if(dialogOptions.currentTarget){
+ $scope.target = dialogOptions.currentTarget;
+ }
+
+ $scope.upload = function(v){
+ angular.element(".umb-file-dropzone-directive .file-select").click();
+ };
+
+ $scope.dragLeave = function(el, event){
+ $scope.activeDrag = false;
+ };
+
+ $scope.dragEnter = function(el, event){
+ $scope.activeDrag = true;
+ };
+
+ $scope.submitFolder = function(e) {
+ if (e.keyCode === 13) {
+ e.preventDefault();
+
+ mediaResource
+ .addFolder($scope.newFolderName, $scope.currentFolder.id)
+ .then(function(data) {
+ $scope.showFolderInput = false;
+ $scope.newFolderName = "";
+
+ //we've added a new folder so lets clear the tree cache for that specific item
+ treeService.clearCache({
+ cacheKey: "__media", //this is the main media tree cache key
+ childrenOf: data.parentId //clear the children of the parent
+ });
+
+ $scope.gotoFolder(data);
+ });
+ }
+ };
+
+ $scope.gotoFolder = function(folder) {
+
+ if(!folder){
+ folder = {id: -1, name: "Media", icon: "icon-folder"};
+ }
+
+ if (folder.id > 0) {
+ entityResource.getAncestors(folder.id, "media")
+ .then(function(anc) {
+ // anc.splice(0,1);
+ $scope.path = _.filter(anc, function (f) {
+ return f.path.indexOf($scope.startNodeId) !== -1;
+ });
+ });
+ }
+ else {
+ $scope.path = [];
+ }
+
+ //mediaResource.rootMedia()
+ mediaResource.getChildren(folder.id)
+ .then(function(data) {
+ $scope.searchTerm = "";
+ $scope.images = data.items ? data.items : [];
+ });
+
+ $scope.currentFolder = folder;
+ };
+
+
+ $scope.clickHandler = function(image, ev, select) {
+ ev.preventDefault();
+
+ if (image.isFolder && !select) {
+ $scope.gotoFolder(image);
+ }else{
+ eventsService.emit("dialogs.mediaPicker.select", image);
+
+ //we have 3 options add to collection (if multi) show details, or submit it right back to the callback
+ if ($scope.multiPicker) {
+ $scope.select(image);
+ image.cssclass = ($scope.dialogData.selection.indexOf(image) > -1) ? "selected" : "";
+ }else if($scope.showDetails) {
+ $scope.target= image;
+ $scope.target.url = mediaHelper.resolveFile(image);
+ }else{
+ $scope.submit(image);
+ }
+ }
+ };
+
+ $scope.exitDetails = function(){
+ if(!$scope.currentFolder){
+ $scope.gotoFolder();
+ }
+
+ $scope.target = undefined;
+ };
+
+ $scope.onUploadComplete = function () {
+ $scope.gotoFolder($scope.currentFolder);
+ };
+
+ $scope.onFilesQueue = function(){
+ $scope.activeDrag = false;
+ };
+
+ //default root item
+ if(!$scope.target){
+ $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" });
+ }
+ });
+//used for the member picker dialog
+angular.module("umbraco").controller("Umbraco.Dialogs.MemberGroupPickerController",
+ function($scope, eventsService, entityResource, searchService, $log) {
+ var dialogOptions = $scope.dialogOptions;
+ $scope.dialogTreeEventHandler = $({});
+ $scope.multiPicker = dialogOptions.multiPicker;
+
+ /** Method used for selecting a node */
+ function select(text, id) {
+
+ if (dialogOptions.multiPicker) {
+ $scope.select(id);
+ }
+ else {
+ $scope.submit(id);
+ }
+ }
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ eventsService.emit("dialogs.memberGroupPicker.select", args);
+
+ //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
+ //from the server in this method.
+ select(args.node.name, args.node.id);
+
+ //toggle checked state
+ args.node.selected = args.node.selected === true ? false : true;
+ }
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+angular.module("umbraco").controller("Umbraco.Dialogs.RteEmbedController", function ($scope, $http, umbRequestHelper) {
+ $scope.form = {};
+ $scope.form.url = "";
+ $scope.form.width = 360;
+ $scope.form.height = 240;
+ $scope.form.constrain = true;
+ $scope.form.preview = "";
+ $scope.form.success = false;
+ $scope.form.info = "";
+ $scope.form.supportsDimensions = false;
+
+ var origWidth = 500;
+ var origHeight = 300;
+
+ $scope.showPreview = function() {
+
+ if ($scope.form.url) {
+ $scope.form.show = true;
+ $scope.form.preview = "";
+ $scope.form.info = "";
+ $scope.form.success = false;
+
+ $http({ method: 'GET', url: umbRequestHelper.getApiUrl("embedApiBaseUrl", "GetEmbed"), params: { url: $scope.form.url, width: $scope.form.width, height: $scope.form.height } })
+ .success(function (data) {
+
+ $scope.form.preview = "";
+
+ switch (data.Status) {
+ case 0:
+ //not supported
+ $scope.form.info = "Not supported";
+ break;
+ case 1:
+ //error
+ $scope.form.info = "Could not embed media - please ensure the URL is valid";
+ break;
+ case 2:
+ $scope.form.preview = data.Markup;
+ $scope.form.supportsDimensions = data.SupportsDimensions;
+ $scope.form.success = true;
+ break;
+ }
+ })
+ .error(function () {
+ $scope.form.supportsDimensions = false;
+ $scope.form.preview = "";
+ $scope.form.info = "Could not embed media - please ensure the URL is valid";
+ });
+ } else {
+ $scope.form.supportsDimensions = false;
+ $scope.form.preview = "";
+ $scope.form.info = "Please enter a URL";
+ }
+ };
+
+ $scope.changeSize = function (type) {
+ var width, height;
+
+ if ($scope.form.constrain) {
+ width = parseInt($scope.form.width, 10);
+ height = parseInt($scope.form.height, 10);
+ if (type == 'width') {
+ origHeight = Math.round((width / origWidth) * height);
+ $scope.form.height = origHeight;
+ } else {
+ origWidth = Math.round((height / origHeight) * width);
+ $scope.form.width = origWidth;
+ }
+ }
+ if ($scope.form.url != "") {
+ $scope.showPreview();
+ }
+
+ };
+
+ $scope.insert = function(){
+ $scope.submit($scope.form.preview);
+ };
+});
+angular.module("umbraco").controller('Umbraco.Dialogs.Template.QueryBuilderController',
+ function($scope, $http, dialogService){
+
+
+ $http.get("backoffice/UmbracoApi/TemplateQuery/GetAllowedProperties").then(function(response) {
+ $scope.properties = response.data;
+ });
+
+ $http.get("backoffice/UmbracoApi/TemplateQuery/GetContentTypes").then(function (response) {
+ $scope.contentTypes = response.data;
+ });
+
+ $http.get("backoffice/UmbracoApi/TemplateQuery/GetFilterConditions").then(function (response) {
+ $scope.conditions = response.data;
+ });
+
+
+ $scope.query = {
+ contentType: {
+ name: "Everything"
+ },
+ source:{
+ name: "My website"
+ },
+ filters:[
+ {
+ property:undefined,
+ operator: undefined
+ }
+ ],
+ sort:{
+ property:{
+ alias: "",
+ name: "",
+ },
+ direction: "ascending"
+ }
+ };
+
+
+
+ $scope.chooseSource = function(query){
+ dialogService.contentPicker({
+ callback: function (data) {
+
+ if (data.id > 0) {
+ query.source = { id: data.id, name: data.name };
+ } else {
+ query.source.name = "My website";
+ delete query.source.id;
+ }
+ }
+ });
+ };
+
+ var throttledFunc = _.throttle(function() {
+
+ $http.post("backoffice/UmbracoApi/TemplateQuery/PostTemplateQuery", $scope.query).then(function (response) {
+ $scope.result = response.data;
+ });
+
+ }, 200);
+
+ $scope.$watch("query", function(value) {
+ throttledFunc();
+ }, true);
+
+ $scope.getPropertyOperators = function (property) {
+
+ var conditions = _.filter($scope.conditions, function(condition) {
+ var index = condition.appliesTo.indexOf(property.type);
+ return index >= 0;
+ });
+ return conditions;
+ };
+
+
+ $scope.addFilter = function(query){
+ query.filters.push({});
+ };
+
+ $scope.trashFilter = function (query) {
+ query.filters.splice(query,1);
+ };
+
+ $scope.changeSortOrder = function(query){
+ if(query.sort.direction === "ascending"){
+ query.sort.direction = "descending";
+ }else{
+ query.sort.direction = "ascending";
+ }
+ };
+
+ $scope.setSortProperty = function(query, property){
+ query.sort.property = property;
+ if(property.type === "datetime"){
+ query.sort.direction = "descending";
+ }else{
+ query.sort.direction = "ascending";
+ }
+ };
+ });
+angular.module("umbraco").controller('Umbraco.Dialogs.Template.SnippetController',
+ function($scope) {
+ $scope.type = $scope.dialogOptions.type;
+ $scope.section = {
+ name: "",
+ required: false
+ };
+ });
+//used for the media picker dialog
+angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController",
+ function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) {
+
+ var tree = null;
+ var dialogOptions = $scope.dialogOptions;
+ $scope.dialogTreeEventHandler = $({});
+ $scope.section = dialogOptions.section;
+ $scope.treeAlias = dialogOptions.treeAlias;
+ $scope.multiPicker = dialogOptions.multiPicker;
+ $scope.hideHeader = true;
+ $scope.searchInfo = {
+ searchFromId: dialogOptions.startNodeId,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ }
+
+ //create the custom query string param for this tree
+ $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : "";
+ $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : "";
+
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
+ var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document";
+
+
+ //min / max values
+ if (dialogOptions.minNumber) {
+ dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
+ }
+ if (dialogOptions.maxNumber) {
+ dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
+ }
+
+ if (dialogOptions.section === "member") {
+ entityType = "Member";
+ }
+ else if (dialogOptions.section === "media") {
+ entityType = "Media";
+ }
+
+ //Configures filtering
+ if (dialogOptions.filter) {
+
+ dialogOptions.filterExclude = false;
+ dialogOptions.filterAdvanced = false;
+
+ //used advanced filtering
+ if (angular.isFunction(dialogOptions.filter)) {
+ dialogOptions.filterAdvanced = true;
+ }
+ else if (angular.isObject(dialogOptions.filter)) {
+ dialogOptions.filterAdvanced = true;
+ }
+ else {
+ if (dialogOptions.filter.startsWith("!")) {
+ dialogOptions.filterExclude = true;
+ dialogOptions.filter = dialogOptions.filter.substring(1);
+ }
+
+ //used advanced filtering
+ if (dialogOptions.filter.startsWith("{")) {
+ dialogOptions.filterAdvanced = true;
+ //convert to object
+ dialogOptions.filter = angular.fromJson(dialogOptions.filter);
+ }
+ }
+ }
+
+ function nodeExpandedHandler(ev, args) {
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+
+ //check if any of the items are list views, if so we need to add some custom
+ // children: A node to activate the search, any nodes that have already been
+ // selected in the search
+ if (child.metaData.isContainer) {
+ child.hasChildren = true;
+ child.children = [
+ {
+ level: child.level + 1,
+ hasChildren: false,
+ parent: function () {
+ return child;
+ },
+ name: searchText,
+ metaData: {
+ listViewNode: child,
+ },
+ cssClass: "icon-search",
+ cssClasses: ["not-published"]
+ }
+ ];
+ //add base transition classes to this node
+ child.cssClasses.push("tree-node-slide-up");
+
+ var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) {
+ return i.parentId == child.id;
+ });
+ _.each(listViewResults, function(item) {
+ child.children.unshift({
+ id: item.id,
+ name: item.name,
+ cssClass: "icon umb-tree-icon sprTree " + item.icon,
+ level: child.level + 1,
+ metaData: {
+ isSearchResult: true
+ },
+ hasChildren: false,
+ parent: function () {
+ return child;
+ }
+ });
+ });
+ }
+
+ //now we need to look in the already selected search results and
+ // toggle the check boxes for those ones that are listed
+ var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
+ return child.id == selected.id;
+ });
+ if (exists) {
+ child.selected = true;
+ }
+ });
+
+ //check filter
+ performFiltering(args.children);
+ }
+ }
+
+ //gets the tree object when it loads
+ function treeLoadedHandler(ev, args) {
+ tree = args.tree;
+ }
+
+ //wires up selection
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.listViewNode) {
+ //check if list view 'search' node was selected
+
+ $scope.searchInfo.showSearch = true;
+ $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
+ $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
+
+ //add transition classes
+ var listViewNode = args.node.parent();
+ listViewNode.cssClasses.push('tree-node-slide-up-hide-active');
+ }
+ else if (args.node.metaData.isSearchResult) {
+ //check if the item selected was a search result from a list view
+
+ //unselect
+ select(args.node.name, args.node.id);
+
+ //remove it from the list view children
+ var listView = args.node.parent();
+ listView.children = _.reject(listView.children, function(child) {
+ return child.id == args.node.id;
+ });
+
+ //remove it from the custom tracked search result list
+ $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.id == args.node.id;
+ });
+ }
+ else {
+ eventsService.emit("dialogs.treePickerController.select", args);
+
+ if (args.node.filtered) {
+ return;
+ }
+
+ //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
+ //from the server in this method.
+ select(args.node.name, args.node.id);
+
+ //toggle checked state
+ args.node.selected = args.node.selected === true ? false : true;
+ }
+ }
+
+ /** Method used for selecting a node */
+ function select(text, id, entity) {
+ //if we get the root, we just return a constructed entity, no need for server data
+ if (id < 0) {
+ if ($scope.multiPicker) {
+ $scope.select(id);
+ }
+ else {
+ var node = {
+ alias: null,
+ icon: "icon-folder",
+ id: id,
+ name: text
+ };
+ $scope.submit(node);
+ }
+ }
+ else {
+
+ if ($scope.multiPicker) {
+ $scope.select(Number(id));
+ }
+ else {
+
+ $scope.hideSearch();
+
+ //if an entity has been passed in, use it
+ if (entity) {
+ $scope.submit(entity);
+ } else {
+ //otherwise we have to get it from the server
+ entityResource.getById(id, entityType).then(function (ent) {
+ $scope.submit(ent);
+ });
+ }
+ }
+ }
+ }
+
+ function performFiltering(nodes) {
+
+ if (!dialogOptions.filter) {
+ return;
+ }
+
+ //remove any list view search nodes from being filtered since these are special nodes that always must
+ // be allowed to be clicked on
+ nodes = _.filter(nodes, function(n) {
+ return !angular.isObject(n.metaData.listViewNode);
+ });
+
+ if (dialogOptions.filterAdvanced) {
+
+ //filter either based on a method or an object
+ var filtered = angular.isFunction(dialogOptions.filter)
+ ? _.filter(nodes, dialogOptions.filter)
+ : _.where(nodes, dialogOptions.filter);
+
+ angular.forEach(filtered, function (value, key) {
+ value.filtered = true;
+ if (dialogOptions.filterCssClass) {
+ if (!value.cssClasses) {
+ value.cssClasses = [];
+ }
+ value.cssClasses.push(dialogOptions.filterCssClass);
+ }
+ });
+ } else {
+ var a = dialogOptions.filter.toLowerCase().split(',');
+ angular.forEach(nodes, function (value, key) {
+
+ var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
+
+ if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
+ value.filtered = true;
+
+ if (dialogOptions.filterCssClass) {
+ if (!value.cssClasses) {
+ value.cssClasses = [];
+ }
+ value.cssClasses.push(dialogOptions.filterCssClass);
+ }
+ }
+ });
+ }
+ }
+
+ $scope.multiSubmit = function (result) {
+ entityResource.getByIds(result, entityType).then(function (ents) {
+ $scope.submit(ents);
+ });
+ };
+
+ /** method to select a search result */
+ $scope.selectResult = function (evt, result) {
+
+ if (result.filtered) {
+ return;
+ }
+
+ result.selected = result.selected === true ? false : true;
+
+ //since result = an entity, we'll pass it in so we don't have to go back to the server
+ select(result.name, result.id, result);
+
+ //add/remove to our custom tracked list of selected search results
+ if (result.selected) {
+ $scope.searchInfo.selectedSearchResults.push(result);
+ }
+ else {
+ $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) {
+ return i.id == result.id;
+ });
+ }
+
+ //ensure the tree node in the tree is checked/unchecked if it already exists there
+ if (tree) {
+ var found = treeService.getDescendantNode(tree.root, result.id);
+ if (found) {
+ found.selected = result.selected;
+ }
+ }
+
+ };
+
+ $scope.hideSearch = function () {
+
+ //Traverse the entire displayed tree and update each node to sync with the selected search results
+ if (tree) {
+
+ //we need to ensure that any currently displayed nodes that get selected
+ // from the search get updated to have a check box!
+ function checkChildren(children) {
+ _.each(children, function (child) {
+ //check if the id is in the selection, if so ensure it's flagged as selected
+ var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
+ return child.id == selected.id;
+ });
+ //if the curr node exists in selected search results, ensure it's checked
+ if (exists) {
+ child.selected = true;
+ }
+ //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
+ else if (child.metaData.isSearchResult) {
+ //if this tree node is under a list view it means that the node was added
+ // to the tree dynamically under the list view that was searched, so we actually want to remove
+ // it all together from the tree
+ var listView = child.parent();
+ listView.children = _.reject(listView.children, function(c) {
+ return c.id == child.id;
+ });
+ }
+
+ //check if the current node is a list view and if so, check if there's any new results
+ // that need to be added as child nodes to it based on search results selected
+ if (child.metaData.isContainer) {
+
+ child.cssClasses = _.reject(child.cssClasses, function(c) {
+ return c === 'tree-node-slide-up-hide-active';
+ });
+
+ var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.parentId == child.id;
+ });
+ _.each(listViewResults, function (item) {
+ var childExists = _.find(child.children, function(c) {
+ return c.id == item.id;
+ });
+ if (!childExists) {
+ var parent = child;
+ child.children.unshift({
+ id: item.id,
+ name: item.name,
+ cssClass: "icon umb-tree-icon sprTree " + item.icon,
+ level: child.level + 1,
+ metaData: {
+ isSearchResult: true
+ },
+ hasChildren: false,
+ parent: function () {
+ return parent;
+ }
+ });
+ }
+ });
+ }
+
+ //recurse
+ if (child.children && child.children.length > 0) {
+ checkChildren(child.children);
+ }
+ });
+ }
+ checkChildren(tree.root.children);
+ }
+
+
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
+
+ $scope.onSearchResults = function(results) {
+
+ //filter all items - this will mark an item as filtered
+ performFiltering(results);
+
+ //now actually remove all filtered items so they are not even displayed
+ results = _.filter(results, function(item) {
+ return !item.filtered;
+ });
+
+ $scope.searchInfo.results = results;
+
+ //sync with the curr selected results
+ _.each($scope.searchInfo.results, function (result) {
+ var exists = _.find($scope.dialogData.selection, function (selectedId) {
+ return result.id == selectedId;
+ });
+ if (exists) {
+ result.selected = true;
+ }
+ });
+
+ $scope.searchInfo.showSearch = true;
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+angular.module("umbraco")
+ .controller("Umbraco.Dialogs.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper) {
+
+ $scope.history = historyService.getCurrent();
+ $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
+ $scope.showPasswordFields = false;
+ $scope.changePasswordButtonState = "init";
+
+ $scope.externalLoginProviders = externalLoginInfo.providers;
+ $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl;
+ var evts = [];
+ evts.push(eventsService.on("historyService.add", function (e, args) {
+ $scope.history = args.all;
+ }));
+ evts.push(eventsService.on("historyService.remove", function (e, args) {
+ $scope.history = args.all;
+ }));
+ evts.push(eventsService.on("historyService.removeAll", function (e, args) {
+ $scope.history = [];
+ }));
+
+ $scope.logout = function () {
+
+ //Add event listener for when there are pending changes on an editor which means our route was not successful
+ var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) {
+ //one time listener, remove the event
+ pendingChangeEvent();
+ $scope.close();
+ });
+
+
+ //perform the path change, if it is successful then the promise will resolve otherwise it will fail
+ $scope.close();
+ $location.path("/logout");
+ };
+
+ $scope.gotoHistory = function (link) {
+ $location.path(link);
+ $scope.close();
+ };
+
+ //Manually update the remaining timeout seconds
+ function updateTimeout() {
+ $timeout(function () {
+ if ($scope.remainingAuthSeconds > 0) {
+ $scope.remainingAuthSeconds--;
+ $scope.$digest();
+ //recurse
+ updateTimeout();
+ }
+
+ }, 1000, false); // 1 second, do NOT execute a global digest
+ }
+
+ function updateUserInfo() {
+ //get the user
+ userService.getCurrentUser().then(function (user) {
+ $scope.user = user;
+ if ($scope.user) {
+ $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
+ $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1;
+ //set the timer
+ updateTimeout();
+
+ authResource.getCurrentUserLinkedLogins().then(function(logins) {
+ //reset all to be un-linked
+ for (var provider in $scope.externalLoginProviders) {
+ $scope.externalLoginProviders[provider].linkedProviderKey = undefined;
+ }
+
+ //set the linked logins
+ for (var login in logins) {
+ var found = _.find($scope.externalLoginProviders, function (i) {
+ return i.authType == login;
+ });
+ if (found) {
+ found.linkedProviderKey = logins[login];
+ }
+ }
+ });
+ }
+ });
+ }
+
+ $scope.unlink = function (e, loginProvider, providerKey) {
+ var result = confirm("Are you sure you want to unlink this account?");
+ if (!result) {
+ e.preventDefault();
+ return;
+ }
+
+ authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) {
+ updateUserInfo();
+ });
+ }
+
+ updateUserInfo();
+
+ //remove all event handlers
+ $scope.$on('$destroy', function () {
+ for (var e = 0; e < evts.length; e++) {
+ evts[e]();
+ }
+
+ });
+
+ /* ---------- UPDATE PASSWORD ---------- */
+
+ //create the initial model for change password property editor
+ $scope.changePasswordModel = {
+ alias: "_umb_password",
+ view: "changepassword",
+ config: {},
+ value: {}
+ };
+
+ //go get the config for the membership provider and add it to the model
+ currentUserResource.getMembershipProviderConfig().then(function(data) {
+ $scope.changePasswordModel.config = data;
+ //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
+ //this will ensure the oldPassword is shown so they can change it
+ // disable reset password functionality beacuse it does not make sense inside the backoffice
+ $scope.changePasswordModel.config.hasPassword = true;
+ $scope.changePasswordModel.config.disableToggle = true;
+ $scope.changePasswordModel.config.enableReset = false;
+ });
+
+ $scope.changePassword = function() {
+
+ if (formHelper.submitForm({ scope: $scope })) {
+
+ $scope.changePasswordButtonState = "busy";
+
+ currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) {
+
+ //if the password has been reset, then update our model
+ if (data.value) {
+ $scope.changePasswordModel.value.generatedPassword = data.value;
+ }
+
+ formHelper.resetForm({ scope: $scope, notifications: data.notifications });
+
+ $scope.changePasswordButtonState = "success";
+
+ }, function (err) {
+
+ formHelper.handleError(err);
+
+ $scope.changePasswordButtonState = "error";
+
+ });
+
+ }
+
+ };
+
+ $scope.togglePasswordFields = function() {
+ clearPasswordFields();
+ $scope.showPasswordFields = !$scope.showPasswordFields;
+ }
+
+ function clearPasswordFields() {
+ $scope.changePasswordModel.value.newPassword = "";
+ $scope.changePasswordModel.confirm = "";
+ }
+
+ });
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Dialogs.LegacyDeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function YsodController($scope, legacyResource, treeService, navigationService) {
+
+ if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) {
+ //trim whitespace
+ $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim();
+ }
+
+ $scope.closeDialog = function() {
+ $scope.dismiss();
+ };
+
+}
+
+angular.module("umbraco").controller("Umbraco.Dialogs.YsodController", YsodController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.LegacyController
+ * @function
+ *
+ * @description
+ * A controller to control the legacy iframe injection
+ *
+*/
+function LegacyController($scope, $routeParams, $element) {
+
+ var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, ""));
+ //split into path and query
+ var urlParts = url.split("?");
+ var extIndex = urlParts[0].lastIndexOf(".");
+ var ext = extIndex === -1 ? "" : urlParts[0].substr(extIndex);
+ //path cannot be a js file
+ if (ext !== ".js" || ext === "") {
+ //path cannot contain any of these chars
+ var toClean = "*(){}[];:<>\\|'\"";
+ for (var i = 0; i < toClean.length; i++) {
+ var reg = new RegExp("\\" + toClean[i], "g");
+ urlParts[0] = urlParts[0].replace(reg, "");
+ }
+ //join cleaned path and query back together
+ url = urlParts[0] + (urlParts.length === 1 ? "" : ("?" + urlParts[1]));
+ $scope.legacyPath = url;
+ }
+ else {
+ throw "Invalid url";
+ }
+}
+
+angular.module("umbraco").controller('Umbraco.LegacyController', LegacyController);
+/** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */
+angular.module('umbraco').controller("Umbraco.LoginController", function (eventsService, $scope, userService, $location, $rootScope) {
+
+ userService._showLoginDialog();
+
+ var evtOn = eventsService.on("app.ready", function(evt, data){
+ $scope.avatar = "assets/img/application/logo.png";
+
+ var path = "/";
+
+ //check if there's a returnPath query string, if so redirect to it
+ var locationObj = $location.search();
+ if (locationObj.returnPath) {
+ path = decodeURIComponent(locationObj.returnPath);
+ }
+
+ $location.url(path);
+ });
+
+ $scope.$on('$destroy', function () {
+ eventsService.unsubscribe(evtOn);
+ });
+
+});
+
+//used for the media picker dialog
+angular.module("umbraco").controller("Umbraco.Notifications.ConfirmRouteChangeController",
+ function ($scope, $location, $log, notificationsService) {
+
+ $scope.discard = function(not){
+ not.args.listener();
+
+ $location.search("");
+
+ //we need to break the path up into path and query
+ var parts = not.args.path.split("?");
+ var query = {};
+ if (parts.length > 1) {
+ _.each(parts[1].split("&"), function(q) {
+ var keyVal = q.split("=");
+ query[keyVal[0]] = keyVal[1];
+ });
+ }
+
+ $location.path(parts[0]).search(query);
+ notificationsService.remove(not);
+ };
+
+ $scope.stay = function(not){
+ notificationsService.remove(not);
+ };
+
+ });
+ (function() {
+ "use strict";
+
+ function CompositionsOverlay($scope) {
+
+ var vm = this;
+
+ vm.isSelected = isSelected;
+
+ function isSelected(alias) {
+ if($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) {
+ return true;
+ }
+ }
+ }
+
+ angular.module("umbraco").controller("Umbraco.Overlays.CompositionsOverlay", CompositionsOverlay);
+
+})();
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.PropertyController
+ * @function
+ *
+ * @description
+ * The controller for the content type editor property dialog
+ */
+
+(function() {
+ "use strict";
+
+ function EditorPickerOverlay($scope, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService) {
+
+ var vm = this;
+
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectEditor");
+ }
+
+ vm.searchTerm = "";
+ vm.showTabs = false;
+ vm.tabsLoaded = 0;
+ vm.typesAndEditors = [];
+ vm.userConfigured = [];
+ vm.loading = false;
+ vm.tabs = [{
+ active: true,
+ id: 1,
+ label: localizationService.localize("contentTypeEditor_availableEditors"),
+ alias: "Default",
+ typesAndEditors: []
+ }, {
+ active: false,
+ id: 2,
+ label: localizationService.localize("contentTypeEditor_reuse"),
+ alias: "Reuse",
+ userConfigured: []
+ }];
+
+ vm.filterItems = filterItems;
+ vm.showDetailsOverlay = showDetailsOverlay;
+ vm.hideDetailsOverlay = hideDetailsOverlay;
+ vm.pickEditor = pickEditor;
+ vm.pickDataType = pickDataType;
+
+ function activate() {
+
+ getGroupedDataTypes();
+ getGroupedPropertyEditors();
+
+ }
+
+ function getGroupedPropertyEditors() {
+
+ vm.loading = true;
+
+ dataTypeResource.getGroupedPropertyEditors().then(function(data) {
+ vm.tabs[0].typesAndEditors = data;
+ vm.typesAndEditors = data;
+ vm.tabsLoaded = vm.tabsLoaded + 1;
+ checkIfTabContentIsLoaded();
+ });
+
+ }
+
+ function getGroupedDataTypes() {
+
+ vm.loading = true;
+
+ dataTypeResource.getGroupedDataTypes().then(function(data) {
+ vm.tabs[1].userConfigured = data;
+ vm.userConfigured = data;
+ vm.tabsLoaded = vm.tabsLoaded + 1;
+ checkIfTabContentIsLoaded();
+ });
+
+ }
+
+ function checkIfTabContentIsLoaded() {
+ if (vm.tabsLoaded === 2) {
+ vm.loading = false;
+ vm.showTabs = true;
+ }
+ }
+
+ function filterItems() {
+ // clear item details
+ $scope.model.itemDetails = null;
+
+ if (vm.searchTerm) {
+ vm.showFilterResult = true;
+ vm.showTabs = false;
+ } else {
+ vm.showFilterResult = false;
+ vm.showTabs = true;
+ }
+
+ }
+
+ function showDetailsOverlay(property) {
+
+ var propertyDetails = {};
+ propertyDetails.icon = property.icon;
+ propertyDetails.title = property.name;
+
+ $scope.model.itemDetails = propertyDetails;
+
+ }
+
+ function hideDetailsOverlay() {
+ $scope.model.itemDetails = null;
+ }
+
+ function pickEditor(editor) {
+
+ var parentId = -1;
+
+ dataTypeResource.getScaffold(parentId).then(function(dataType) {
+
+ // set alias
+ dataType.selectedEditor = editor.alias;
+
+ // set name
+ var nameArray = [];
+
+ if ($scope.model.contentTypeName) {
+ nameArray.push($scope.model.contentTypeName);
+ }
+
+ if ($scope.model.property.label) {
+ nameArray.push($scope.model.property.label);
+ }
+
+ if (editor.name) {
+ nameArray.push(editor.name);
+ }
+
+ // make name
+ dataType.name = nameArray.join(" - ");
+
+ // get pre values
+ dataTypeResource.getPreValues(dataType.selectedEditor).then(function(preValues) {
+
+ dataType.preValues = preValues;
+
+ openEditorSettingsOverlay(dataType, true);
+
+ });
+
+ });
+
+ }
+
+ function pickDataType(selectedDataType) {
+
+ dataTypeResource.getById(selectedDataType.id).then(function(dataType) {
+ contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function(propertyType) {
+ submitOverlay(dataType, propertyType, false);
+ });
+ });
+
+ }
+
+ function openEditorSettingsOverlay(dataType, isNew) {
+ vm.editorSettingsOverlay = {
+ title: localizationService.localize("contentTypeEditor_editorSettings"),
+ dataType: dataType,
+ view: "views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html",
+ show: true,
+ submit: function(model) {
+ var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
+
+ dataTypeResource.save(model.dataType, preValues, isNew).then(function(newDataType) {
+
+ contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function(propertyType) {
+
+ submitOverlay(newDataType, propertyType, true);
+
+ vm.editorSettingsOverlay.show = false;
+ vm.editorSettingsOverlay = null;
+
+ });
+
+ });
+ }
+ };
+
+ }
+
+ function submitOverlay(dataType, propertyType, isNew) {
+
+ // update property
+ $scope.model.property.config = propertyType.config;
+ $scope.model.property.editor = propertyType.editor;
+ $scope.model.property.view = propertyType.view;
+ $scope.model.property.dataTypeId = dataType.id;
+ $scope.model.property.dataTypeIcon = dataType.icon;
+ $scope.model.property.dataTypeName = dataType.name;
+
+ $scope.model.updateSameDataTypes = isNew;
+
+ $scope.model.submit($scope.model);
+
+ }
+
+ activate();
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Overlays.EditorPickerOverlay", EditorPickerOverlay);
+
+})();
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.PropertyController
+ * @function
+ *
+ * @description
+ * The controller for the content type editor property dialog
+ */
+
+ (function() {
+ "use strict";
+
+ function PropertySettingsOverlay($scope, contentTypeResource, dataTypeResource, dataTypeHelper, localizationService) {
+
+ var vm = this;
+
+ vm.showValidationPattern = false;
+ vm.focusOnPatternField = false;
+ vm.focusOnMandatoryField = false;
+ vm.selectedValidationType = {};
+ vm.validationTypes = [
+ {
+ "name": localizationService.localize("validation_validateAsEmail"),
+ "key": "email",
+ "pattern": "[a-zA-Z0-9_\.\+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-\.]+",
+ "enableEditing": true
+ },
+ {
+ "name": localizationService.localize("validation_validateAsNumber"),
+ "key": "number",
+ "pattern": "^[0-9]*$",
+ "enableEditing": true
+ },
+ {
+ "name": localizationService.localize("validation_validateAsUrl"),
+ "key": "url",
+ "pattern": "https?\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}",
+ "enableEditing": true
+ },
+ {
+ "name": localizationService.localize("validation_enterCustomValidation"),
+ "key": "custom",
+ "pattern": "",
+ "enableEditing": true
+ }
+ ];
+
+ vm.changeValidationType = changeValidationType;
+ vm.changeValidationPattern = changeValidationPattern;
+ vm.openEditorPickerOverlay = openEditorPickerOverlay;
+ vm.openEditorSettingsOverlay = openEditorSettingsOverlay;
+
+ function activate() {
+
+ matchValidationType();
+
+ }
+
+ function changeValidationPattern() {
+ matchValidationType();
+ }
+
+ function openEditorPickerOverlay(property) {
+
+ vm.focusOnMandatoryField = false;
+
+ vm.editorPickerOverlay = {};
+ vm.editorPickerOverlay.property = $scope.model.property;
+ vm.editorPickerOverlay.contentTypeName = $scope.model.contentTypeName;
+ vm.editorPickerOverlay.view = "views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html";
+ vm.editorPickerOverlay.show = true;
+
+ vm.editorPickerOverlay.submit = function(model) {
+
+ $scope.model.updateSameDataTypes = model.updateSameDataTypes;
+
+ vm.focusOnMandatoryField = true;
+
+ // update property
+ property.config = model.property.config;
+ property.editor = model.property.editor;
+ property.view = model.property.view;
+ property.dataTypeId = model.property.dataTypeId;
+ property.dataTypeIcon = model.property.dataTypeIcon;
+ property.dataTypeName = model.property.dataTypeName;
+
+ vm.editorPickerOverlay.show = false;
+ vm.editorPickerOverlay = null;
+ };
+
+ vm.editorPickerOverlay.close = function(model) {
+ vm.editorPickerOverlay.show = false;
+ vm.editorPickerOverlay = null;
+ };
+
+ }
+
+ function openEditorSettingsOverlay(property) {
+
+ vm.focusOnMandatoryField = false;
+
+ // get data type
+ dataTypeResource.getById(property.dataTypeId).then(function(dataType) {
+
+ vm.editorSettingsOverlay = {};
+ vm.editorSettingsOverlay.title = "Editor settings";
+ vm.editorSettingsOverlay.view = "views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html";
+ vm.editorSettingsOverlay.dataType = dataType;
+ vm.editorSettingsOverlay.show = true;
+
+ vm.editorSettingsOverlay.submit = function(model) {
+
+ var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
+
+ dataTypeResource.save(model.dataType, preValues, false).then(function(newDataType) {
+
+ contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function(propertyType) {
+
+ // update editor
+ property.config = propertyType.config;
+ property.editor = propertyType.editor;
+ property.view = propertyType.view;
+ property.dataTypeId = newDataType.id;
+ property.dataTypeIcon = newDataType.icon;
+ property.dataTypeName = newDataType.name;
+
+ // set flag to update same data types
+ $scope.model.updateSameDataTypes = true;
+
+ vm.focusOnMandatoryField = true;
+
+ vm.editorSettingsOverlay.show = false;
+ vm.editorSettingsOverlay = null;
+
+ });
+
+ });
+
+ };
+
+ vm.editorSettingsOverlay.close = function(oldModel) {
+ vm.editorSettingsOverlay.show = false;
+ vm.editorSettingsOverlay = null;
+ };
+
+ });
+
+ }
+
+ function matchValidationType() {
+
+ if($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== "" && $scope.model.property.validation.pattern !== undefined) {
+
+ var match = false;
+
+ // find and show if a match from the list has been chosen
+ angular.forEach(vm.validationTypes, function(validationType, index){
+ if($scope.model.property.validation.pattern === validationType.pattern) {
+ vm.selectedValidationType = vm.validationTypes[index];
+ vm.showValidationPattern = true;
+ match = true;
+ }
+ });
+
+ // if there is no match - choose the custom validation option.
+ if(!match) {
+ angular.forEach(vm.validationTypes, function(validationType){
+ if(validationType.key === "custom") {
+ vm.selectedValidationType = validationType;
+ vm.showValidationPattern = true;
+ }
+ });
+ }
+ }
+
+ }
+
+ function changeValidationType(selectedValidationType) {
+
+ if(selectedValidationType) {
+ $scope.model.property.validation.pattern = selectedValidationType.pattern;
+ vm.showValidationPattern = true;
+
+ // set focus on textarea
+ if(selectedValidationType.key === "custom") {
+ vm.focusOnPatternField = true;
+ }
+
+ } else {
+ $scope.model.property.validation.pattern = "";
+ vm.showValidationPattern = false;
+ }
+
+ }
+
+ activate();
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Overlay.PropertySettingsOverlay", PropertySettingsOverlay);
+
+})();
+
+ (function() {
+ "use strict";
+
+ function CopyOverlay($scope, localizationService, eventsService) {
+
+ var vm = this;
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("general_copy");
+ }
+
+ vm.hideSearch = hideSearch;
+ vm.selectResult = selectResult;
+ vm.onSearchResults = onSearchResults;
+
+ var dialogOptions = $scope.model;
+ var searchText = "Search...";
+ var node = dialogOptions.currentNode;
+
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ $scope.model.relateToOriginal = true;
+ $scope.dialogTreeEventHandler = $({});
+
+ vm.searchInfo = {
+ searchFromId: null,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ };
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.listViewNode) {
+ //check if list view 'search' node was selected
+
+ vm.searchInfo.showSearch = true;
+ vm.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
+ vm.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
+ }
+ else {
+ //eventsService.emit("editors.content.copyController.select", args);
+
+ if ($scope.model.target) {
+ //un-select if there's a current one selected
+ $scope.model.target.selected = false;
+ }
+
+ $scope.model.target = args.node;
+ $scope.model.target.selected = true;
+ }
+
+ }
+
+ function nodeExpandedHandler(ev, args) {
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+ //check if any of the items are list views, if so we need to add a custom
+ // child: A node to activate the search
+ if (child.metaData.isContainer) {
+ child.hasChildren = true;
+ child.children = [
+ {
+ level: child.level + 1,
+ hasChildren: false,
+ name: searchText,
+ metaData: {
+ listViewNode: child,
+ },
+ cssClass: "icon umb-tree-icon sprTree icon-search",
+ cssClasses: ["not-published"]
+ }
+ ];
+ }
+ });
+ }
+ }
+
+ function hideSearch() {
+ vm.searchInfo.showSearch = false;
+ vm.searchInfo.searchFromId = null;
+ vm.searchInfo.searchFromName = null;
+ vm.searchInfo.results = [];
+ }
+
+ // method to select a search result
+ function selectResult(evt, result) {
+ result.selected = result.selected === true ? false : true;
+ nodeSelectHandler(evt, { event: evt, node: result });
+ }
+
+ //callback when there are search results
+ function onSearchResults(results) {
+ vm.searchInfo.results = results;
+ vm.searchInfo.showSearch = true;
+ }
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ });
+
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Overlays.CopyOverlay", CopyOverlay);
+
+})();
+
+(function() {
+ "use strict";
+
+ function EmbedOverlay($scope, $http, umbRequestHelper, localizationService) {
+
+ var vm = this;
+ var origWidth = 500;
+ var origHeight = 300;
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("general_embed");
+ }
+
+ $scope.model.embed = {
+ url: "",
+ width: 360,
+ height: 240,
+ constrain: true,
+ preview: "",
+ success: false,
+ info: "",
+ supportsDimensions: ""
+ };
+
+ vm.showPreview = showPreview;
+ vm.changeSize = changeSize;
+
+ function showPreview() {
+
+ if ($scope.model.embed.url) {
+ $scope.model.embed.show = true;
+ $scope.model.embed.preview = "";
+ $scope.model.embed.info = "";
+ $scope.model.embed.success = false;
+
+ $http({
+ method: 'GET',
+ url: umbRequestHelper.getApiUrl("embedApiBaseUrl", "GetEmbed"),
+ params: {
+ url: $scope.model.embed.url,
+ width: $scope.model.embed.width,
+ height: $scope.model.embed.height
+ }
+ })
+ .success(function(data) {
+
+ $scope.model.embed.preview = "";
+
+ switch (data.Status) {
+ case 0:
+ //not supported
+ $scope.model.embed.info = "Not supported";
+ break;
+ case 1:
+ //error
+ $scope.model.embed.info = "Could not embed media - please ensure the URL is valid";
+ break;
+ case 2:
+ $scope.model.embed.preview = data.Markup;
+ $scope.model.embed.supportsDimensions = data.SupportsDimensions;
+ $scope.model.embed.success = true;
+ break;
+ }
+ })
+ .error(function() {
+ $scope.model.embed.supportsDimensions = false;
+ $scope.model.embed.preview = "";
+ $scope.model.embed.info = "Could not embed media - please ensure the URL is valid";
+ });
+ } else {
+ $scope.model.embed.supportsDimensions = false;
+ $scope.model.embed.preview = "";
+ $scope.model.embed.info = "Please enter a URL";
+ }
+ }
+
+ function changeSize(type) {
+
+ var width, height;
+
+ if ($scope.model.embed.constrain) {
+ width = parseInt($scope.model.embed.width, 10);
+ height = parseInt($scope.model.embed.height, 10);
+ if (type == 'width') {
+ origHeight = Math.round((width / origWidth) * height);
+ $scope.model.embed.height = origHeight;
+ } else {
+ origWidth = Math.round((height / origHeight) * width);
+ $scope.model.embed.width = origWidth;
+ }
+ }
+ if ($scope.model.embed.url !== "") {
+ showPreview();
+ }
+
+ }
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Overlays.EmbedOverlay", EmbedOverlay);
+
+})();
+
+angular.module("umbraco")
+ .controller("Umbraco.Overlays.HelpController", function ($scope, $location, $routeParams, helpService, userService, localizationService) {
+ $scope.section = $routeParams.section;
+ $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
+ $scope.model.subtitle = "Umbraco version" + " " + $scope.version;
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("general_help");
+ }
+
+ if(!$scope.section){
+ $scope.section = "content";
+ }
+
+ $scope.sectionName = $scope.section;
+
+ var rq = {};
+ rq.section = $scope.section;
+
+ //translate section name
+ localizationService.localize("sections_" + rq.section).then(function (value) {
+ $scope.sectionName = value;
+ });
+
+ userService.getCurrentUser().then(function(user){
+
+ rq.usertype = user.userType;
+ rq.lang = user.locale;
+
+ if($routeParams.url){
+ rq.path = decodeURIComponent($routeParams.url);
+
+ if(rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0){
+ rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
+ }
+
+ if(rq.path.indexOf(".aspx") > 0){
+ rq.path = rq.path.substring(0, rq.path.indexOf(".aspx"));
+ }
+
+ }else{
+ rq.path = rq.section + "/" + $routeParams.tree + "/" + $routeParams.method;
+ }
+
+ helpService.findHelp(rq).then(function(topics){
+ $scope.topics = topics;
+ });
+
+ helpService.findVideos(rq).then(function(videos){
+ $scope.videos = videos;
+ });
+
+ });
+
+
+ });
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.PropertyController
+ * @function
+ *
+ * @description
+ * The controller for the content type editor property dialog
+ */
+function IconPickerOverlay($scope, iconHelper, localizationService) {
+
+ $scope.loading = true;
+ $scope.model.hideSubmitButton = true;
+
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectIcon");
+ }
+
+ iconHelper.getIcons().then(function(icons) {
+ $scope.icons = icons;
+ $scope.loading = false;
+ });
+
+ $scope.selectIcon = function(icon, color) {
+ $scope.model.icon = icon;
+ $scope.model.color = color;
+ $scope.submitForm($scope.model);
+ };
+
+}
+
+angular.module("umbraco").controller("Umbraco.Overlays.IconPickerOverlay", IconPickerOverlay);
+
+function ItemPickerOverlay($scope, localizationService) {
+
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectItem");
+ }
+
+ $scope.model.hideSubmitButton = true;
+
+ $scope.selectItem = function(item) {
+ $scope.model.selectedItem = item;
+ $scope.submitForm($scope.model);
+ };
+
+}
+
+angular.module("umbraco").controller("Umbraco.Overlays.ItemPickerOverlay", ItemPickerOverlay);
+
+//used for the media picker dialog
+angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
+ function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
+ var dialogOptions = $scope.model;
+
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectLink");
+ }
+
+ $scope.dialogTreeEventHandler = $({});
+ $scope.model.target = {};
+ $scope.searchInfo = {
+ searchFromId: null,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ };
+
+ if (dialogOptions.currentTarget) {
+ $scope.model.target = dialogOptions.currentTarget;
+
+ //if we have a node ID, we fetch the current node to build the form data
+ if ($scope.model.target.id) {
+
+ if (!$scope.model.target.path) {
+ entityResource.getPath($scope.model.target.id, "Document").then(function (path) {
+ $scope.model.target.path = path;
+ //now sync the tree to this path
+ $scope.dialogTreeEventHandler.syncTree({ path: $scope.model.target.path, tree: "content" });
+ });
+ }
+
+ contentResource.getNiceUrl($scope.model.target.id).then(function (url) {
+ $scope.model.target.url = url;
+ });
+ }
+ }
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.listViewNode) {
+ //check if list view 'search' node was selected
+
+ $scope.searchInfo.showSearch = true;
+ $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
+ $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
+ }
+ else {
+ eventsService.emit("dialogs.linkPicker.select", args);
+
+ if ($scope.currentNode) {
+ //un-select if there's a current one selected
+ $scope.currentNode.selected = false;
+ }
+
+ $scope.currentNode = args.node;
+ $scope.currentNode.selected = true;
+ $scope.model.target.id = args.node.id;
+ $scope.model.target.name = args.node.name;
+
+ if (args.node.id < 0) {
+ $scope.model.target.url = "/";
+ }
+ else {
+ contentResource.getNiceUrl(args.node.id).then(function (url) {
+ $scope.model.target.url = url;
+ });
+ }
+
+ if (!angular.isUndefined($scope.model.target.isMedia)) {
+ delete $scope.model.target.isMedia;
+ }
+ }
+ }
+
+ function nodeExpandedHandler(ev, args) {
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+ //check if any of the items are list views, if so we need to add a custom
+ // child: A node to activate the search
+ if (child.metaData.isContainer) {
+ child.hasChildren = true;
+ child.children = [
+ {
+ level: child.level + 1,
+ hasChildren: false,
+ name: searchText,
+ metaData: {
+ listViewNode: child,
+ },
+ cssClass: "icon umb-tree-icon sprTree icon-search",
+ cssClasses: ["not-published"]
+ }
+ ];
+ }
+ });
+ }
+ }
+
+ $scope.switchToMediaPicker = function () {
+ userService.getCurrentUser().then(function (userData) {
+ $scope.mediaPickerOverlay = {
+ view: "mediapicker",
+ startNodeId: userData.startMediaId,
+ show: true,
+ submit: function(model) {
+ var media = model.selectedImages[0];
+
+ $scope.model.target.id = media.id;
+ $scope.model.target.isMedia = true;
+ $scope.model.target.name = media.name;
+ $scope.model.target.url = mediaHelper.resolveFile(media);
+
+ $scope.mediaPickerOverlay.show = false;
+ $scope.mediaPickerOverlay = null;
+ }
+ };
+ });
+ };
+
+ $scope.hideSearch = function () {
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = null;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
+
+ // method to select a search result
+ $scope.selectResult = function (evt, result) {
+ result.selected = result.selected === true ? false : true;
+ nodeSelectHandler(evt, {event: evt, node: result});
+ };
+
+ //callback when there are search results
+ $scope.onSearchResults = function (results) {
+ $scope.searchInfo.results = results;
+ $scope.searchInfo.showSearch = true;
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ });
+ });
+
+function MacroPickerController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper, localizationService) {
+
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMacro");
+ }
+
+ $scope.macros = [];
+ $scope.model.selectedMacro = null;
+ $scope.wizardStep = "macroSelect";
+ $scope.model.macroParams = [];
+ $scope.noMacroParams = false;
+
+ $scope.changeMacro = function() {
+ if ($scope.wizardStep === "macroSelect") {
+ editParams();
+ } else {
+ submitForm();
+ }
+ };
+
+ /** changes the view to edit the params of the selected macro */
+ function editParams() {
+ //get the macro params if there are any
+ macroResource.getMacroParameters($scope.model.selectedMacro.id)
+ .then(function (data) {
+
+ //go to next page if there are params otherwise we can just exit
+ if (!angular.isArray(data) || data.length === 0) {
+
+ $scope.noMacroParams = true;
+
+ } else {
+ $scope.wizardStep = "paramSelect";
+ $scope.model.macroParams = data;
+
+ //fill in the data if we are editing this macro
+ if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroParamsDictionary) {
+ _.each($scope.model.dialogData.macroData.macroParamsDictionary, function (val, key) {
+ var prop = _.find($scope.model.macroParams, function (item) {
+ return item.alias == key;
+ });
+ if (prop) {
+
+ if (_.isString(val)) {
+ //we need to unescape values as they have most likely been escaped while inserted
+ val = _.unescape(val);
+
+ //detect if it is a json string
+ if (val.detectIsJson()) {
+ try {
+ //Parse it to json
+ prop.value = angular.fromJson(val);
+ }
+ catch (e) {
+ // not json
+ prop.value = val;
+ }
+ }
+ else {
+ prop.value = val;
+ }
+ }
+ else {
+ prop.value = val;
+ }
+ }
+ });
+
+ }
+ }
+
+ });
+ }
+
+ //here we check to see if we've been passed a selected macro and if so we'll set the
+ //editor to start with parameter editing
+ if ($scope.model.dialogData && $scope.model.dialogData.macroData) {
+ $scope.wizardStep = "paramSelect";
+ }
+
+ //get the macro list - pass in a filter if it is only for rte
+ entityResource.getAll("Macro", ($scope.model.dialogData && $scope.model.dialogData.richTextEditor && $scope.model.dialogData.richTextEditor === true) ? "UseInEditor=true" : null)
+ .then(function (data) {
+
+ //if 'allowedMacros' is specified, we need to filter
+ if (angular.isArray($scope.model.dialogData.allowedMacros) && $scope.model.dialogData.allowedMacros.length > 0) {
+ $scope.macros = _.filter(data, function(d) {
+ return _.contains($scope.model.dialogData.allowedMacros, d.alias);
+ });
+ }
+ else {
+ $scope.macros = data;
+ }
+
+
+ //check if there's a pre-selected macro and if it exists
+ if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroAlias) {
+ var found = _.find(data, function (item) {
+ return item.alias === $scope.model.dialogData.macroData.macroAlias;
+ });
+ if (found) {
+ //select the macro and go to next screen
+ $scope.model.selectedMacro = found;
+ editParams();
+ return;
+ }
+ }
+ //we don't have a pre-selected macro so ensure the correct step is set
+ $scope.wizardStep = "macroSelect";
+ });
+
+
+}
+
+angular.module("umbraco").controller("Umbraco.Overlays.MacroPickerController", MacroPickerController);
+
+//used for the media picker dialog
+angular.module("umbraco")
+ .controller("Umbraco.Overlays.MediaPickerController",
+ function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, eventsService, treeService, $element, $timeout, $cookies, $cookieStore, localizationService) {
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
+ }
+
+ var dialogOptions = $scope.model;
+
+ $scope.disableFolderSelect = dialogOptions.disableFolderSelect;
+ $scope.onlyImages = dialogOptions.onlyImages;
+ $scope.showDetails = dialogOptions.showDetails;
+ $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false;
+ $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
+ $scope.cropSize = dialogOptions.cropSize;
+ $scope.lastOpenedNode = $cookieStore.get("umbLastOpenedMediaNodeId");
+ if($scope.onlyImages){
+ $scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
+ }
+ else {
+ $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles);
+ }
+ $scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";
+
+ $scope.model.selectedImages = [];
+
+ //preload selected item
+ $scope.target = undefined;
+ if(dialogOptions.currentTarget){
+ $scope.target = dialogOptions.currentTarget;
+ }
+
+ $scope.upload = function(v){
+ angular.element(".umb-file-dropzone-directive .file-select").click();
+ };
+
+ $scope.dragLeave = function(el, event){
+ $scope.activeDrag = false;
+ };
+
+ $scope.dragEnter = function(el, event){
+ $scope.activeDrag = true;
+ };
+
+ $scope.submitFolder = function() {
+
+ if ($scope.newFolderName) {
+
+ mediaResource
+ .addFolder($scope.newFolderName, $scope.currentFolder.id)
+ .then(function(data) {
+
+ //we've added a new folder so lets clear the tree cache for that specific item
+ treeService.clearCache({
+ cacheKey: "__media", //this is the main media tree cache key
+ childrenOf: data.parentId //clear the children of the parent
+ });
+
+ $scope.gotoFolder(data);
+
+ $scope.showFolderInput = false;
+
+ $scope.newFolderName = "";
+
+ });
+
+ } else {
+ $scope.showFolderInput = false;
+ }
+
+ };
+
+ $scope.enterSubmitFolder = function(event) {
+ if (event.keyCode === 13) {
+ $scope.submitFolder();
+ event.stopPropagation();
+ }
+ };
+
+ $scope.gotoFolder = function(folder) {
+
+ if(!$scope.multiPicker) {
+ deselectAllImages($scope.model.selectedImages);
+ }
+
+ if(!folder){
+ folder = {id: -1, name: "Media", icon: "icon-folder"};
+ }
+
+ if (folder.id > 0) {
+ entityResource.getAncestors(folder.id, "media")
+ .then(function(anc) {
+ // anc.splice(0,1);
+ $scope.path = _.filter(anc, function (f) {
+ return f.path.indexOf($scope.startNodeId) !== -1;
+ });
+ });
+ }
+ else {
+ $scope.path = [];
+ }
+
+ //mediaResource.rootMedia()
+ mediaResource.getChildren(folder.id)
+ .then(function(data) {
+ $scope.searchTerm = "";
+ $scope.images = data.items ? data.items : [];
+
+ // set already selected images to selected
+ for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) {
+
+ var folderImage = $scope.images[folderImageIndex];
+ var imageIsSelected = false;
+
+ for (var selectedImageIndex = 0; selectedImageIndex < $scope.model.selectedImages.length; selectedImageIndex++) {
+ var selectedImage = $scope.model.selectedImages[selectedImageIndex];
+
+ if(folderImage.key === selectedImage.key) {
+ imageIsSelected = true;
+ }
+ }
+
+ if(imageIsSelected) {
+ folderImage.selected = true;
+ }
+ }
+
+ });
+
+ $scope.currentFolder = folder;
+
+ // for some reason i cannot set cookies with cookieStore
+ document.cookie="umbLastOpenedMediaNodeId=" + folder.id;
+
+ };
+
+ $scope.clickHandler = function(image, event, index) {
+ if (image.isFolder) {
+ if ($scope.disableFolderSelect) {
+ $scope.gotoFolder(image);
+ } else {
+ eventsService.emit("dialogs.mediaPicker.select", image);
+ selectImage(image);
+ }
+
+ } else {
+
+ eventsService.emit("dialogs.mediaPicker.select", image);
+
+ if($scope.showDetails) {
+ $scope.target = image;
+ $scope.target.url = mediaHelper.resolveFile(image);
+ $scope.openDetailsDialog();
+ } else {
+ selectImage(image);
+ }
+
+ }
+
+ };
+
+ $scope.clickItemName = function(item) {
+ if(item.isFolder) {
+ $scope.gotoFolder(item);
+ }
+ };
+
+ function selectImage(image) {
+
+ if(image.selected) {
+
+ for(var i = 0; $scope.model.selectedImages.length > i; i++) {
+
+ var imageInSelection = $scope.model.selectedImages[i];
+ if(image.key === imageInSelection.key) {
+ image.selected = false;
+ $scope.model.selectedImages.splice(i, 1);
+ }
+ }
+
+ } else {
+
+ if(!$scope.multiPicker) {
+ deselectAllImages($scope.model.selectedImages);
+ }
+
+ image.selected = true;
+ $scope.model.selectedImages.push(image);
+ }
+
+ }
+
+ function deselectAllImages(images) {
+ for (var i = 0; i < images.length; i++) {
+ var image = images[i];
+ image.selected = false;
+ }
+ images.length = 0;
+ }
+
+ $scope.onUploadComplete = function () {
+ $scope.gotoFolder($scope.currentFolder);
+ };
+
+ $scope.onFilesQueue = function(){
+ $scope.activeDrag = false;
+ };
+
+ //default root item
+ if (!$scope.target) {
+
+ if($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) {
+
+ entityResource.getById($scope.lastOpenedNode, "media")
+ .then(function(node){
+
+ // make sure that las opened node is on the same path as start node
+ var nodePath = node.path.split(",");
+
+ if(nodePath.indexOf($scope.startNodeId.toString()) !== -1) {
+ $scope.gotoFolder({id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder"});
+ } else {
+ $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"});
+ }
+
+ }, function (err) {
+ $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"});
+ });
+
+ } else {
+
+ $scope.gotoFolder({id: $scope.startNodeId, name: "Media", icon: "icon-folder"});
+
+ }
+
+ }
+
+ $scope.openDetailsDialog = function() {
+
+ $scope.mediaPickerDetailsOverlay = {};
+ $scope.mediaPickerDetailsOverlay.show = true;
+
+ $scope.mediaPickerDetailsOverlay.submit = function(model) {
+
+ $scope.model.selectedImages.push($scope.target);
+ $scope.model.submit($scope.model);
+
+ $scope.mediaPickerDetailsOverlay.show = false;
+ $scope.mediaPickerDetailsOverlay = null;
+
+ };
+
+ $scope.mediaPickerDetailsOverlay.close = function(oldModel) {
+ $scope.mediaPickerDetailsOverlay.show = false;
+ $scope.mediaPickerDetailsOverlay = null;
+ };
+
+ };
+
+
+ });
+
+//used for the member picker dialog
+angular.module("umbraco").controller("Umbraco.Overlays.MemberGroupPickerController",
+ function($scope, eventsService, entityResource, searchService, $log, localizationService) {
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMemberGroup");
+ }
+
+ $scope.dialogTreeEventHandler = $({});
+ $scope.multiPicker = $scope.model.multiPicker;
+
+ function activate() {
+
+ if($scope.multiPicker) {
+ $scope.model.selectedMemberGroups = [];
+ } else {
+ $scope.model.selectedMemberGroup = "";
+ }
+
+ }
+
+ function selectMemberGroup(id) {
+ $scope.model.selectedMemberGroup = id;
+ }
+
+ function selectMemberGroups(id) {
+ $scope.model.selectedMemberGroups.push(id);
+ }
+
+ /** Method used for selecting a node */
+ function select(text, id) {
+
+ if ($scope.model.multiPicker) {
+ selectMemberGroups(id);
+ }
+ else {
+ selectMemberGroup(id);
+ $scope.model.submit($scope.model);
+ }
+ }
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ eventsService.emit("dialogs.memberGroupPicker.select", args);
+
+ //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
+ //from the server in this method.
+ select(args.node.name, args.node.id);
+
+ //toggle checked state
+ args.node.selected = args.node.selected === true ? false : true;
+ }
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+
+ activate();
+
+ });
+
+ (function() {
+ "use strict";
+
+ function MoveOverlay($scope, localizationService, eventsService) {
+
+ var vm = this;
+
+ vm.hideSearch = hideSearch;
+ vm.selectResult = selectResult;
+ vm.onSearchResults = onSearchResults;
+
+ var dialogOptions = $scope.model;
+ var searchText = "Search...";
+ var node = dialogOptions.currentNode;
+
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("actions_move");
+ }
+
+ $scope.model.relateToOriginal = true;
+ $scope.dialogTreeEventHandler = $({});
+
+ vm.searchInfo = {
+ searchFromId: null,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ };
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.listViewNode) {
+ //check if list view 'search' node was selected
+
+ vm.searchInfo.showSearch = true;
+ vm.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
+ vm.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
+ }
+ else {
+ //eventsService.emit("editors.content.copyController.select", args);
+
+ if ($scope.model.target) {
+ //un-select if there's a current one selected
+ $scope.model.target.selected = false;
+ }
+
+ $scope.model.target = args.node;
+ $scope.model.target.selected = true;
+ }
+
+ }
+
+ function nodeExpandedHandler(ev, args) {
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+ //check if any of the items are list views, if so we need to add a custom
+ // child: A node to activate the search
+ if (child.metaData.isContainer) {
+ child.hasChildren = true;
+ child.children = [
+ {
+ level: child.level + 1,
+ hasChildren: false,
+ name: searchText,
+ metaData: {
+ listViewNode: child,
+ },
+ cssClass: "icon umb-tree-icon sprTree icon-search",
+ cssClasses: ["not-published"]
+ }
+ ];
+ }
+ });
+ }
+ }
+
+ function hideSearch() {
+ vm.searchInfo.showSearch = false;
+ vm.searchInfo.searchFromId = null;
+ vm.searchInfo.searchFromName = null;
+ vm.searchInfo.results = [];
+ }
+
+ // method to select a search result
+ function selectResult(evt, result) {
+ result.selected = result.selected === true ? false : true;
+ nodeSelectHandler(evt, { event: evt, node: result });
+ }
+
+ //callback when there are search results
+ function onSearchResults(results) {
+ vm.searchInfo.results = results;
+ vm.searchInfo.showSearch = true;
+ }
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ });
+
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Overlays.MoveOverlay", MoveOverlay);
+
+})();
+
+//used for the media picker dialog
+angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
+ function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) {
+
+ var tree = null;
+ var dialogOptions = $scope.model;
+ $scope.dialogTreeEventHandler = $({});
+ $scope.section = dialogOptions.section;
+ $scope.treeAlias = dialogOptions.treeAlias;
+ $scope.multiPicker = dialogOptions.multiPicker;
+ $scope.hideHeader = true;
+ $scope.searchInfo = {
+ searchFromId: dialogOptions.startNodeId,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ }
+
+ $scope.model.selection = [];
+
+ $scope.init = function(contentType) {
+
+ if(contentType === "content") {
+ entityType = "Document";
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectContent");
+ }
+ } else if(contentType === "member") {
+ entityType = "Member";
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMember");
+ }
+ } else if(contentType === "media") {
+ entityType = "Media";
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
+ }
+ }
+ }
+
+ //create the custom query string param for this tree
+ $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : "";
+ $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : "";
+
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
+ var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document";
+
+
+ //min / max values
+ if (dialogOptions.minNumber) {
+ dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
+ }
+ if (dialogOptions.maxNumber) {
+ dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
+ }
+
+ if (dialogOptions.section === "member") {
+ entityType = "Member";
+ }
+ else if (dialogOptions.section === "media") {
+ entityType = "Media";
+ }
+
+ //Configures filtering
+ if (dialogOptions.filter) {
+
+ dialogOptions.filterExclude = false;
+ dialogOptions.filterAdvanced = false;
+
+ //used advanced filtering
+ if (angular.isFunction(dialogOptions.filter)) {
+ dialogOptions.filterAdvanced = true;
+ }
+ else if (angular.isObject(dialogOptions.filter)) {
+ dialogOptions.filterAdvanced = true;
+ }
+ else {
+ if (dialogOptions.filter.startsWith("!")) {
+ dialogOptions.filterExclude = true;
+ dialogOptions.filter = dialogOptions.filter.substring(1);
+ }
+
+ //used advanced filtering
+ if (dialogOptions.filter.startsWith("{")) {
+ dialogOptions.filterAdvanced = true;
+ //convert to object
+ dialogOptions.filter = angular.fromJson(dialogOptions.filter);
+ }
+ }
+ }
+
+ function nodeExpandedHandler(ev, args) {
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+
+ //check if any of the items are list views, if so we need to add some custom
+ // children: A node to activate the search, any nodes that have already been
+ // selected in the search
+ if (child.metaData.isContainer) {
+ child.hasChildren = true;
+ child.children = [
+ {
+ level: child.level + 1,
+ hasChildren: false,
+ parent: function () {
+ return child;
+ },
+ name: searchText,
+ metaData: {
+ listViewNode: child,
+ },
+ cssClass: "icon-search",
+ cssClasses: ["not-published"]
+ }
+ ];
+ //add base transition classes to this node
+ child.cssClasses.push("tree-node-slide-up");
+
+ var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) {
+ return i.parentId == child.id;
+ });
+ _.each(listViewResults, function(item) {
+ child.children.unshift({
+ id: item.id,
+ name: item.name,
+ cssClass: "icon umb-tree-icon sprTree " + item.icon,
+ level: child.level + 1,
+ metaData: {
+ isSearchResult: true
+ },
+ hasChildren: false,
+ parent: function () {
+ return child;
+ }
+ });
+ });
+ }
+
+ //now we need to look in the already selected search results and
+ // toggle the check boxes for those ones that are listed
+ var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
+ return child.id == selected.id;
+ });
+ if (exists) {
+ child.selected = true;
+ }
+ });
+
+ //check filter
+ performFiltering(args.children);
+ }
+ }
+
+ //gets the tree object when it loads
+ function treeLoadedHandler(ev, args) {
+ tree = args.tree;
+ }
+
+ //wires up selection
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.listViewNode) {
+ //check if list view 'search' node was selected
+
+ $scope.searchInfo.showSearch = true;
+ $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
+ $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
+
+ //add transition classes
+ var listViewNode = args.node.parent();
+ listViewNode.cssClasses.push('tree-node-slide-up-hide-active');
+ }
+ else if (args.node.metaData.isSearchResult) {
+ //check if the item selected was a search result from a list view
+
+ //unselect
+ select(args.node.name, args.node.id);
+
+ //remove it from the list view children
+ var listView = args.node.parent();
+ listView.children = _.reject(listView.children, function(child) {
+ return child.id == args.node.id;
+ });
+
+ //remove it from the custom tracked search result list
+ $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.id == args.node.id;
+ });
+ }
+ else {
+ eventsService.emit("dialogs.treePickerController.select", args);
+
+ if (args.node.filtered) {
+ return;
+ }
+
+ //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
+ //from the server in this method.
+ select(args.node.name, args.node.id);
+
+ //toggle checked state
+ args.node.selected = args.node.selected === true ? false : true;
+ }
+ }
+
+ /** Method used for selecting a node */
+ function select(text, id, entity) {
+ //if we get the root, we just return a constructed entity, no need for server data
+ if (id < 0) {
+ if ($scope.multiPicker) {
+
+ if (entity) {
+ multiSelectItem(entity);
+ } else {
+ //otherwise we have to get it from the server
+ entityResource.getById(id, entityType).then(function (ent) {
+ multiSelectItem(ent);
+ });
+ }
+
+ }
+ else {
+ var node = {
+ alias: null,
+ icon: "icon-folder",
+ id: id,
+ name: text
+ };
+ $scope.model.selection.push(node);
+ $scope.model.submit($scope.model);
+ }
+ }
+ else {
+
+ if ($scope.multiPicker) {
+
+ if (entity) {
+ multiSelectItem(entity);
+ } else {
+ //otherwise we have to get it from the server
+ entityResource.getById(id, entityType).then(function (ent) {
+ multiSelectItem(ent);
+ });
+ }
+
+ }
+
+ else {
+
+ $scope.hideSearch();
+
+ //if an entity has been passed in, use it
+ if (entity) {
+ $scope.model.selection.push(entity);
+ $scope.model.submit($scope.model);
+ } else {
+ //otherwise we have to get it from the server
+ entityResource.getById(id, entityType).then(function (ent) {
+ $scope.model.selection.push(ent);
+ $scope.model.submit($scope.model);
+ });
+ }
+ }
+ }
+ }
+
+ function multiSelectItem(item) {
+
+ var found = false;
+ var foundIndex = 0;
+
+ if($scope.model.selection.length > 0) {
+ for(i = 0; $scope.model.selection.length > i; i++) {
+ var selectedItem = $scope.model.selection[i];
+ if(selectedItem.id === item.id) {
+ found = true;
+ foundIndex = i;
+ }
+ }
+ }
+
+ if(found) {
+ $scope.model.selection.splice(foundIndex, 1);
+ } else {
+ $scope.model.selection.push(item);
+ }
+
+ }
+
+ function performFiltering(nodes) {
+
+ if (!dialogOptions.filter) {
+ return;
+ }
+
+ //remove any list view search nodes from being filtered since these are special nodes that always must
+ // be allowed to be clicked on
+ nodes = _.filter(nodes, function(n) {
+ return !angular.isObject(n.metaData.listViewNode);
+ });
+
+ if (dialogOptions.filterAdvanced) {
+
+ //filter either based on a method or an object
+ var filtered = angular.isFunction(dialogOptions.filter)
+ ? _.filter(nodes, dialogOptions.filter)
+ : _.where(nodes, dialogOptions.filter);
+
+ angular.forEach(filtered, function (value, key) {
+ value.filtered = true;
+ if (dialogOptions.filterCssClass) {
+ if (!value.cssClasses) {
+ value.cssClasses = [];
+ }
+ value.cssClasses.push(dialogOptions.filterCssClass);
+ }
+ });
+ } else {
+ var a = dialogOptions.filter.toLowerCase().split(',');
+ angular.forEach(nodes, function (value, key) {
+
+ var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
+
+ if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
+ value.filtered = true;
+
+ if (dialogOptions.filterCssClass) {
+ if (!value.cssClasses) {
+ value.cssClasses = [];
+ }
+ value.cssClasses.push(dialogOptions.filterCssClass);
+ }
+ }
+ });
+ }
+ }
+
+ $scope.multiSubmit = function (result) {
+ entityResource.getByIds(result, entityType).then(function (ents) {
+ $scope.submit(ents);
+ });
+ };
+
+ /** method to select a search result */
+ $scope.selectResult = function (evt, result) {
+
+ if (result.filtered) {
+ return;
+ }
+
+ result.selected = result.selected === true ? false : true;
+
+ //since result = an entity, we'll pass it in so we don't have to go back to the server
+ select(result.name, result.id, result);
+
+ //add/remove to our custom tracked list of selected search results
+ if (result.selected) {
+ $scope.searchInfo.selectedSearchResults.push(result);
+ }
+ else {
+ $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) {
+ return i.id == result.id;
+ });
+ }
+
+ //ensure the tree node in the tree is checked/unchecked if it already exists there
+ if (tree) {
+ var found = treeService.getDescendantNode(tree.root, result.id);
+ if (found) {
+ found.selected = result.selected;
+ }
+ }
+
+ };
+
+ $scope.hideSearch = function () {
+
+ //Traverse the entire displayed tree and update each node to sync with the selected search results
+ if (tree) {
+
+ //we need to ensure that any currently displayed nodes that get selected
+ // from the search get updated to have a check box!
+ function checkChildren(children) {
+ _.each(children, function (child) {
+ //check if the id is in the selection, if so ensure it's flagged as selected
+ var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
+ return child.id == selected.id;
+ });
+ //if the curr node exists in selected search results, ensure it's checked
+ if (exists) {
+ child.selected = true;
+ }
+ //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
+ else if (child.metaData.isSearchResult) {
+ //if this tree node is under a list view it means that the node was added
+ // to the tree dynamically under the list view that was searched, so we actually want to remove
+ // it all together from the tree
+ var listView = child.parent();
+ listView.children = _.reject(listView.children, function(c) {
+ return c.id == child.id;
+ });
+ }
+
+ //check if the current node is a list view and if so, check if there's any new results
+ // that need to be added as child nodes to it based on search results selected
+ if (child.metaData.isContainer) {
+
+ child.cssClasses = _.reject(child.cssClasses, function(c) {
+ return c === 'tree-node-slide-up-hide-active';
+ });
+
+ var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.parentId == child.id;
+ });
+ _.each(listViewResults, function (item) {
+ var childExists = _.find(child.children, function(c) {
+ return c.id == item.id;
+ });
+ if (!childExists) {
+ var parent = child;
+ child.children.unshift({
+ id: item.id,
+ name: item.name,
+ cssClass: "icon umb-tree-icon sprTree " + item.icon,
+ level: child.level + 1,
+ metaData: {
+ isSearchResult: true
+ },
+ hasChildren: false,
+ parent: function () {
+ return parent;
+ }
+ });
+ }
+ });
+ }
+
+ //recurse
+ if (child.children && child.children.length > 0) {
+ checkChildren(child.children);
+ }
+ });
+ }
+ checkChildren(tree.root.children);
+ }
+
+
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
+
+ $scope.onSearchResults = function(results) {
+
+ //filter all items - this will mark an item as filtered
+ performFiltering(results);
+
+ //now actually remove all filtered items so they are not even displayed
+ results = _.filter(results, function(item) {
+ return !item.filtered;
+ });
+
+ $scope.searchInfo.results = results;
+
+ //sync with the curr selected results
+ _.each($scope.searchInfo.results, function (result) {
+ var exists = _.find($scope.model.selection, function (selectedId) {
+ return result.id == selectedId;
+ });
+ if (exists) {
+ result.selected = true;
+ }
+ });
+
+ $scope.searchInfo.showSearch = true;
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+
+angular.module("umbraco")
+ .controller("Umbraco.Overlays.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper, localizationService) {
+
+ $scope.history = historyService.getCurrent();
+ $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
+ $scope.showPasswordFields = false;
+ $scope.changePasswordButtonState = "init";
+ $scope.model.subtitle = "Umbraco version" + " " + $scope.version;
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("general_user");
+ }
+
+ $scope.externalLoginProviders = externalLoginInfo.providers;
+ $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl;
+ var evts = [];
+ evts.push(eventsService.on("historyService.add", function (e, args) {
+ $scope.history = args.all;
+ }));
+ evts.push(eventsService.on("historyService.remove", function (e, args) {
+ $scope.history = args.all;
+ }));
+ evts.push(eventsService.on("historyService.removeAll", function (e, args) {
+ $scope.history = [];
+ }));
+
+ $scope.logout = function () {
+
+ //Add event listener for when there are pending changes on an editor which means our route was not successful
+ var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) {
+ //one time listener, remove the event
+ pendingChangeEvent();
+ $scope.model.close();
+ });
+
+
+ //perform the path change, if it is successful then the promise will resolve otherwise it will fail
+ $scope.model.close();
+ $location.path("/logout");
+ };
+
+ $scope.gotoHistory = function (link) {
+ $location.path(link);
+ $scope.model.close();
+ };
+
+ //Manually update the remaining timeout seconds
+ function updateTimeout() {
+ $timeout(function () {
+ if ($scope.remainingAuthSeconds > 0) {
+ $scope.remainingAuthSeconds--;
+ $scope.$digest();
+ //recurse
+ updateTimeout();
+ }
+
+ }, 1000, false); // 1 second, do NOT execute a global digest
+ }
+
+ function updateUserInfo() {
+ //get the user
+ userService.getCurrentUser().then(function (user) {
+ $scope.user = user;
+ if ($scope.user) {
+ $scope.model.title = user.name;
+ $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
+ $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1;
+ //set the timer
+ updateTimeout();
+
+ authResource.getCurrentUserLinkedLogins().then(function(logins) {
+ //reset all to be un-linked
+ for (var provider in $scope.externalLoginProviders) {
+ $scope.externalLoginProviders[provider].linkedProviderKey = undefined;
+ }
+
+ //set the linked logins
+ for (var login in logins) {
+ var found = _.find($scope.externalLoginProviders, function (i) {
+ return i.authType == login;
+ });
+ if (found) {
+ found.linkedProviderKey = logins[login];
+ }
+ }
+ });
+ }
+ });
+ }
+
+ $scope.unlink = function (e, loginProvider, providerKey) {
+ var result = confirm("Are you sure you want to unlink this account?");
+ if (!result) {
+ e.preventDefault();
+ return;
+ }
+
+ authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) {
+ updateUserInfo();
+ });
+ }
+
+ updateUserInfo();
+
+ //remove all event handlers
+ $scope.$on('$destroy', function () {
+ for (var e = 0; e < evts.length; e++) {
+ evts[e]();
+ }
+
+ });
+
+ /* ---------- UPDATE PASSWORD ---------- */
+
+ //create the initial model for change password property editor
+ $scope.changePasswordModel = {
+ alias: "_umb_password",
+ view: "changepassword",
+ config: {},
+ value: {}
+ };
+
+ //go get the config for the membership provider and add it to the model
+ currentUserResource.getMembershipProviderConfig().then(function(data) {
+ $scope.changePasswordModel.config = data;
+ //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
+ //this will ensure the oldPassword is shown so they can change it
+ // disable reset password functionality beacuse it does not make sense inside the backoffice
+ $scope.changePasswordModel.config.hasPassword = true;
+ $scope.changePasswordModel.config.disableToggle = true;
+ $scope.changePasswordModel.config.enableReset = false;
+ });
+
+ $scope.changePassword = function() {
+
+ if (formHelper.submitForm({ scope: $scope })) {
+
+ $scope.changePasswordButtonState = "busy";
+
+ currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) {
+
+ //if the password has been reset, then update our model
+ if (data.value) {
+ $scope.changePasswordModel.value.generatedPassword = data.value;
+ }
+
+ formHelper.resetForm({ scope: $scope, notifications: data.notifications });
+
+ $scope.changePasswordButtonState = "success";
+ $timeout(function() {
+ $scope.togglePasswordFields();
+ }, 2000);
+
+ }, function (err) {
+
+ formHelper.handleError(err);
+
+ $scope.changePasswordButtonState = "error";
+
+ });
+
+ }
+
+ };
+
+ $scope.togglePasswordFields = function() {
+ clearPasswordFields();
+ $scope.showPasswordFields = !$scope.showPasswordFields;
+ }
+
+ function clearPasswordFields() {
+ $scope.changePasswordModel.value.newPassword = "";
+ $scope.changePasswordModel.confirm = "";
+ }
+
+ });
+
+angular.module("umbraco")
+ .controller("Umbraco.Overlays.YsodController", function ($scope, legacyResource, treeService, navigationService, localizationService) {
+
+ if(!$scope.model.title) {
+ $scope.model.title = localizationService.localize("errors_receivedErrorFromServer");
+ }
+
+ if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) {
+ //trim whitespace
+ $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim();
+ }
+
+ if ($scope.model.error && $scope.model.error.data) {
+ $scope.model.error.data.InnerExceptions = [];
+ var ex = $scope.model.error.data.InnerException;
+ while (ex) {
+ if (ex.StackTrace) {
+ ex.StackTrace = ex.StackTrace.trim();
+ }
+ $scope.model.error.data.InnerExceptions.push(ex);
+ ex = ex.InnerException;
+ }
+ }
+
+ });
+
+angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
+ function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) {
+
+ var dialogOptions = $scope.dialogOptions;
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ $scope.relateToOriginal = true;
+ $scope.recursive = true;
+ $scope.dialogTreeEventHandler = $({});
+ $scope.busy = false;
+ $scope.searchInfo = {
+ searchFromId: null,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ }
+
+ var node = dialogOptions.currentNode;
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.listViewNode) {
+ //check if list view 'search' node was selected
+
+ $scope.searchInfo.showSearch = true;
+ $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
+ $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
+ }
+ else {
+ eventsService.emit("editors.content.copyController.select", args);
+
+ if ($scope.target) {
+ //un-select if there's a current one selected
+ $scope.target.selected = false;
+ }
+
+ $scope.target = args.node;
+ $scope.target.selected = true;
+ }
+
+ }
+
+ function nodeExpandedHandler(ev, args) {
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+ //check if any of the items are list views, if so we need to add a custom
+ // child: A node to activate the search
+ if (child.metaData.isContainer) {
+ child.hasChildren = true;
+ child.children = [
+ {
+ level: child.level + 1,
+ hasChildren: false,
+ name: searchText,
+ metaData: {
+ listViewNode: child,
+ },
+ cssClass: "icon umb-tree-icon sprTree icon-search",
+ cssClasses: ["not-published"]
+ }
+ ];
+ }
+ });
+ }
+ }
+
+ $scope.hideSearch = function () {
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = null;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
+
+ // method to select a search result
+ $scope.selectResult = function (evt, result) {
+ result.selected = result.selected === true ? false : true;
+ nodeSelectHandler(evt, { event: evt, node: result });
+ };
+
+ //callback when there are search results
+ $scope.onSearchResults = function (results) {
+ $scope.searchInfo.results = results;
+ $scope.searchInfo.showSearch = true;
+ };
+
+ $scope.copy = function () {
+
+ $scope.busy = true;
+ $scope.error = false;
+
+ contentResource.copy({ parentId: $scope.target.id, id: node.id, relateToOriginal: $scope.relateToOriginal, recursive: $scope.recursive })
+ .then(function (path) {
+ $scope.error = false;
+ $scope.success = true;
+ $scope.busy = false;
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the copied content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
+
+ navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ $scope.busy = false;
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ });
+ });
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Content.CreateController
+ * @function
+ *
+ * @description
+ * The controller for the content creation dialog
+ */
+function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper) {
+
+ contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
+ $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
+ });
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.Content.CreateController", contentCreateController);
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.ContentDeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) {
+
+ $scope.performDelete = function() {
+
+ // stop from firing again on double-click
+ if ($scope.busy) { return false; }
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ $scope.busy = true;
+
+ contentResource.deleteById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ treeService.removeNode($scope.currentNode);
+
+ if (rootNode) {
+ //ensure the recycle bin has child nodes now
+ var recycleBin = treeService.getDescendantNode(rootNode, -20);
+ if (recycleBin) {
+ recycleBin.hasChildren = true;
+ }
+ }
+
+ //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
+ if (editorState.current && editorState.current.id == $scope.currentNode.id) {
+
+ //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent
+ var location = "/content";
+ if ($scope.currentNode.parentId.toString() !== "-1")
+ location = "/content/content/edit/" + $scope.currentNode.parentId;
+
+ $location.path(location);
+ }
+
+ navigationService.hideMenu();
+ }, function(err) {
+
+ $scope.currentNode.loading = false;
+ $scope.busy = false;
+
+ //check if response is ysod
+ if (err.status && err.status >= 500) {
+ dialogService.ysodDialog(err);
+ }
+
+ if (err.data && angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", ContentDeleteController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Content.EditController
+ * @function
+ *
+ * @description
+ * The controller for the content editor
+ */
+function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {
+
+ //setup scope vars
+ $scope.defaultButton = null;
+ $scope.subButtons = [];
+
+ $scope.page = {};
+ $scope.page.loading = false;
+ $scope.page.menu = {};
+ $scope.page.menu.currentNode = null;
+ $scope.page.menu.currentSection = appState.getSectionState("currentSection");
+ $scope.page.listViewPath = null;
+ $scope.page.isNew = $routeParams.create;
+ $scope.page.buttonGroupState = "init";
+
+ function init(content) {
+
+ var buttons = contentEditingHelper.configureContentEditorButtons({
+ create: $routeParams.create,
+ content: content,
+ methods: {
+ saveAndPublish: $scope.saveAndPublish,
+ sendToPublish: $scope.sendToPublish,
+ save: $scope.save,
+ unPublish: $scope.unPublish
+ }
+ });
+ $scope.defaultButton = buttons.defaultButton;
+ $scope.subButtons = buttons.subButtons;
+
+ editorState.set($scope.content);
+
+ //We fetch all ancestors of the node to generate the footer breadcrumb navigation
+ if (!$routeParams.create) {
+ if (content.parentId && content.parentId != -1) {
+ entityResource.getAncestors(content.id, "document")
+ .then(function (anc) {
+ $scope.ancestors = anc;
+ });
+ }
+ }
+ }
+
+ /** Syncs the content item to it's tree node - this occurs on first load and after saving */
+ function syncTreeNode(content, path, initialLoad) {
+
+ if (!$scope.content.isChildOfListView) {
+ navigationService.syncTree({ tree: "content", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
+ $scope.page.menu.currentNode = syncArgs.node;
+ });
+ }
+ else if (initialLoad === true) {
+
+ //it's a child item, just sync the ui node to the parent
+ navigationService.syncTree({ tree: "content", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
+
+ //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
+ // from the server so that we can load in the actions menu.
+ umbRequestHelper.resourcePromise(
+ $http.get(content.treeNodeUrl),
+ 'Failed to retrieve data for child node ' + content.id).then(function (node) {
+ $scope.page.menu.currentNode = node;
+ });
+ }
+ }
+
+ // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
+ function performSave(args) {
+ var deferred = $q.defer();
+
+ $scope.page.buttonGroupState = "busy";
+
+ contentEditingHelper.contentEditorPerformSave({
+ statusMessage: args.statusMessage,
+ saveMethod: args.saveMethod,
+ scope: $scope,
+ content: $scope.content,
+ action: args.action
+ }).then(function (data) {
+ //success
+ init($scope.content);
+ syncTreeNode($scope.content, data.path);
+
+ $scope.page.buttonGroupState = "success";
+
+ deferred.resolve(data);
+ }, function (err) {
+ //error
+ if (err) {
+ editorState.set($scope.content);
+ }
+
+ $scope.page.buttonGroupState = "error";
+
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ }
+
+ function resetLastListPageNumber(content) {
+ // We're using rootScope to store the page number for list views, so if returning to the list
+ // we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children
+ // we should remove this so as not to confuse if navigating to a different list
+ if (!content.isChildOfListView && !content.isContainer) {
+ $rootScope.lastListViewPageViewed = null;
+ }
+ }
+
+ if ($routeParams.create) {
+
+ $scope.page.loading = true;
+
+ //we are creating so get an empty content item
+ contentResource.getScaffold($routeParams.id, $routeParams.doctype)
+ .then(function (data) {
+
+ $scope.content = data;
+
+ init($scope.content);
+
+ resetLastListPageNumber($scope.content);
+
+ $scope.page.loading = false;
+
+ });
+ }
+ else {
+
+ $scope.page.loading = true;
+
+ //we are editing so get the content item from the server
+ contentResource.getById($routeParams.id)
+ .then(function (data) {
+
+ $scope.content = data;
+
+ if (data.isChildOfListView && data.trashed === false) {
+ $scope.page.listViewPath = ($routeParams.page)
+ ? "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page
+ : "/content/content/edit/" + data.parentId;
+ }
+
+ init($scope.content);
+
+ //in one particular special case, after we've created a new item we redirect back to the edit
+ // route but there might be server validation errors in the collection which we need to display
+ // after the redirect, so we will bind all subscriptions which will show the server validation errors
+ // if there are any and then clear them so the collection no longer persists them.
+ serverValidationManager.executeAndClearAllSubscriptions();
+
+ syncTreeNode($scope.content, data.path, true);
+
+ resetLastListPageNumber($scope.content);
+
+ $scope.page.loading = false;
+
+ });
+ }
+
+
+ $scope.unPublish = function () {
+
+ if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) {
+
+ $scope.page.buttonGroupState = "busy";
+
+ contentResource.unPublish($scope.content.id)
+ .then(function (data) {
+
+ formHelper.resetForm({ scope: $scope, notifications: data.notifications });
+
+ contentEditingHelper.handleSuccessfulSave({
+ scope: $scope,
+ savedContent: data,
+ rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
+ });
+
+ init($scope.content);
+
+ syncTreeNode($scope.content, data.path);
+
+ $scope.page.buttonGroupState = "success";
+
+ });
+ }
+
+ };
+
+ $scope.sendToPublish = function () {
+ return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending...", action: "sendToPublish" });
+ };
+
+ $scope.saveAndPublish = function () {
+ return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" });
+ };
+
+ $scope.save = function () {
+ return performSave({ saveMethod: contentResource.save, statusMessage: "Saving...", action: "save" });
+ };
+
+ $scope.preview = function (content) {
+
+
+ if (!$scope.busy) {
+
+ // Chromes popup blocker will kick in if a window is opened
+ // outwith the initial scoped request. This trick will fix that.
+ //
+ var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview');
+ $scope.save().then(function (data) {
+ // Build the correct path so both /#/ and #/ work.
+ var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + data.id;
+ previewWindow.location.href = redirect;
+ });
+
+
+ }
+
+ };
+
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Content.EditController", ContentEditController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Content.EmptyRecycleBinController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService, notificationsService, $route) {
+
+ $scope.busy = false;
+
+ $scope.performDelete = function() {
+
+ //(used in the UI)
+ $scope.busy = true;
+ $scope.currentNode.loading = true;
+
+ contentResource.emptyRecycleBin($scope.currentNode.id).then(function (result) {
+
+ $scope.busy = false;
+ $scope.currentNode.loading = false;
+
+ //show any notifications
+ if (angular.isArray(result.notifications)) {
+ for (var i = 0; i < result.notifications.length; i++) {
+ notificationsService.showNotification(result.notifications[i]);
+ }
+ }
+
+ treeService.removeChildNodes($scope.currentNode);
+ navigationService.hideMenu();
+
+ //reload the current view
+ $route.reload();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Content.EmptyRecycleBinController", ContentEmptyRecycleBinController);
+
+angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController",
+ function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) {
+
+ var dialogOptions = $scope.dialogOptions;
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ $scope.dialogTreeEventHandler = $({});
+ $scope.busy = false;
+ $scope.searchInfo = {
+ searchFromId: null,
+ searchFromName: null,
+ showSearch: false,
+ results: [],
+ selectedSearchResults: []
+ }
+
+ var node = dialogOptions.currentNode;
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.listViewNode) {
+ //check if list view 'search' node was selected
+
+ $scope.searchInfo.showSearch = true;
+ $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
+ $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
+ }
+ else {
+ eventsService.emit("editors.content.moveController.select", args);
+
+ if ($scope.target) {
+ //un-select if there's a current one selected
+ $scope.target.selected = false;
+ }
+
+ $scope.target = args.node;
+ $scope.target.selected = true;
+ }
+ }
+
+ function nodeExpandedHandler(ev, args) {
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+ //check if any of the items are list views, if so we need to add a custom
+ // child: A node to activate the search
+ if (child.metaData.isContainer) {
+ child.hasChildren = true;
+ child.children = [
+ {
+ level: child.level + 1,
+ hasChildren: false,
+ name: searchText,
+ metaData: {
+ listViewNode: child,
+ },
+ cssClass: "icon umb-tree-icon sprTree icon-search",
+ cssClasses: ["not-published"]
+ }
+ ];
+ }
+ });
+ }
+ }
+
+ $scope.hideSearch = function () {
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = null;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
+
+ // method to select a search result
+ $scope.selectResult = function (evt, result) {
+ result.selected = result.selected === true ? false : true;
+ nodeSelectHandler(evt, { event: evt, node: result });
+ };
+
+ //callback when there are search results
+ $scope.onSearchResults = function (results) {
+ $scope.searchInfo.results = results;
+ $scope.searchInfo.showSearch = true;
+ };
+
+ $scope.move = function () {
+
+ $scope.busy = true;
+ $scope.error = false;
+
+ contentResource.move({ parentId: $scope.target.id, id: node.id })
+ .then(function (path) {
+ $scope.error = false;
+ $scope.success = true;
+ $scope.busy = false;
+
+ //first we need to remove the node that launched the dialog
+ treeService.removeNode($scope.currentNode);
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the moved content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
+
+ navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ $scope.busy = false;
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ });
+ });
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Content.RecycleBinController
+ * @function
+ *
+ * @description
+ * Controls the recycle bin for content
+ *
+ */
+
+function ContentRecycleBinController($scope, $routeParams, contentResource, navigationService, localizationService) {
+
+ //ensures the list view doesn't actually load until we query for the list view config
+ // for the section
+ $scope.page = {};
+ $scope.page.name = "Recycle Bin";
+ $scope.page.nameLocked = true;
+
+ //ensures the list view doesn't actually load until we query for the list view config
+ // for the section
+ $scope.listViewPath = null;
+
+ $routeParams.id = "-20";
+ contentResource.getRecycleBin().then(function (result) {
+ //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a
+ // single property, so we'll extract that property (list view) and use it's data.
+ var listproperty = result.tabs[0].properties[0];
+
+ _.each(listproperty.config, function (val, key) {
+ $scope.model.config[key] = val;
+ });
+ $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
+ });
+
+ $scope.model = { config: { entityType: $routeParams.section, layouts: [] } };
+
+ // sync tree node
+ navigationService.syncTree({ tree: "content", path: ["-1", $routeParams.id], forceReload: false });
+
+ localizePageName();
+
+ function localizePageName() {
+
+ var pageName = "general_recycleBin";
+
+ localizationService.localize(pageName).then(function (value) {
+ $scope.page.name = value;
+ });
+
+ }
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.Content.RecycleBinController", ContentRecycleBinController);
+
+angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController",
+ function ($scope, relationResource, contentResource, navigationService, appState, treeService) {
+ var dialogOptions = $scope.dialogOptions;
+
+ var node = dialogOptions.currentNode;
+
+ $scope.error = null;
+ $scope.success = false;
+
+ relationResource.getByChildId(node.id, "relateParentDocumentOnDelete").then(function (data) {
+
+ if (data.length == 0) {
+ $scope.success = false;
+ $scope.error = {
+ errorMsg: "Cannot automatically restore this item",
+ data: {
+ Message: "There is no 'restore' relation found for this node. Use the Move menu item to move it manually."
+ }
+ }
+ return;
+ }
+
+ $scope.relation = data[0];
+
+ if ($scope.relation.parentId == -1) {
+ $scope.target = { id: -1, name: "Root" };
+
+ } else {
+ contentResource.getById($scope.relation.parentId).then(function (data) {
+ $scope.target = data;
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ });
+ }
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ });
+
+ $scope.restore = function () {
+ // this code was copied from `content.move.controller.js`
+ contentResource.move({ parentId: $scope.target.id, id: node.id })
+ .then(function (path) {
+
+ $scope.success = true;
+
+ //first we need to remove the node that launched the dialog
+ treeService.removeNode($scope.currentNode);
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the moved content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
+
+ navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ });
+ };
+ });
+function startUpVideosDashboardController($scope, xmlhelper, $log, $http) {
+ $scope.videos = [];
+ $scope.init = function(url){
+ var proxyUrl = "dashboard/feedproxy.aspx?url=" + url;
+ $http.get(proxyUrl).then(function(data){
+ var feed = $(data.data);
+ $('item', feed).each(function (i, item) {
+ var video = {};
+ video.thumbnail = $(item).find('thumbnail').attr('url');
+ video.title = $("title", item).text();
+ video.link = $("guid", item).text();
+ $scope.videos.push(video);
+ });
+ });
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Dashboard.StartupVideosController", startUpVideosDashboardController);
+
+
+function startUpDynamicContentController(dashboardResource, assetsService) {
+ var vm = this;
+ vm.loading = true;
+ vm.showDefault = false;
+
+ //proxy remote css through the local server
+ assetsService.loadCss( dashboardResource.getRemoteDashboardCssUrl("content") );
+ dashboardResource.getRemoteDashboardContent("content").then(
+ function (data) {
+
+ vm.loading = false;
+
+ //test if we have received valid data
+ //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code
+ if (data && data.sections) {
+ vm.dashboard = data;
+ } else{
+ vm.showDefault = true;
+ }
+
+ },
+
+ function (exception) {
+ console.error(exception);
+ vm.loading = false;
+ vm.showDefault = true;
+ });
+}
+
+angular.module("umbraco").controller("Umbraco.Dashboard.StartUpDynamicContentController", startUpDynamicContentController);
+
+
+function FormsController($scope, $route, $cookieStore, packageResource, localizationService) {
+ $scope.installForms = function(){
+ $scope.state = localizationService.localize("packager_installStateDownloading");
+ packageResource
+ .fetch("CD44CF39-3D71-4C19-B6EE-948E1FAF0525")
+ .then(function(pack) {
+ $scope.state = localizationService.localize("packager_installStateImporting");
+ return packageResource.import(pack);
+ },
+ $scope.error)
+ .then(function(pack) {
+ $scope.state = localizationService.localize("packager_installStateInstalling");
+ return packageResource.installFiles(pack);
+ },
+ $scope.error)
+ .then(function(pack) {
+ $scope.state = localizationService.localize("packager_installStateRestarting");
+ return packageResource.installData(pack);
+ },
+ $scope.error)
+ .then(function(pack) {
+ $scope.state = localizationService.localize("packager_installStateComplete");
+ return packageResource.cleanUp(pack);
+ },
+ $scope.error)
+ .then($scope.complete, $scope.error);
+ };
+
+ $scope.complete = function(result){
+ var url = window.location.href + "?init=true";
+ $cookieStore.put("umbPackageInstallId", result.packageGuid);
+ window.location.reload(true);
+ };
+
+ $scope.error = function(err){
+ $scope.state = undefined;
+ $scope.error = err;
+ //This will return a rejection meaning that the promise change above will stop
+ return $q.reject();
+ };
+
+
+ function Video_player (videoId) {
+ // Get dom elements
+ this.container = document.getElementById(videoId);
+ this.video = this.container.getElementsByTagName('video')[0];
+
+ //Create controls
+ this.controls = document.createElement('div');
+ this.controls.className="video-controls";
+
+ this.seek_bar = document.createElement('input');
+ this.seek_bar.className="seek-bar";
+ this.seek_bar.type="range";
+ this.seek_bar.setAttribute('value', '0');
+
+ this.loader = document.createElement('div');
+ this.loader.className="loader";
+
+ this.progress_bar = document.createElement('span');
+ this.progress_bar.className="progress-bar";
+
+ // Insert controls
+ this.controls.appendChild(this.seek_bar);
+ this.container.appendChild(this.controls);
+ this.controls.appendChild(this.loader);
+ this.loader.appendChild(this.progress_bar);
+ }
+
+
+ Video_player.prototype
+ .seeking = function() {
+ // get the value of the seekbar (hidden input[type="range"])
+ var time = this.video.duration * (this.seek_bar.value / 100);
+
+ // Update video to seekbar value
+ this.video.currentTime = time;
+ };
+
+ // Stop video when user initiates seeking
+ Video_player.prototype
+ .start_seek = function() {
+ this.video.pause();
+ };
+
+ // Start video when user stops seeking
+ Video_player.prototype
+ .stop_seek = function() {
+ this.video.play();
+ };
+
+ // Update the progressbar (span.loader) according to video.currentTime
+ Video_player.prototype
+ .update_progress_bar = function() {
+ // Get video progress in %
+ var value = (100 / this.video.duration) * this.video.currentTime;
+
+ // Update progressbar
+ this.progress_bar.style.width = value + '%';
+ };
+
+ // Bind progressbar to mouse when seeking
+ Video_player.prototype
+ .handle_mouse_move = function(event) {
+ // Get position of progressbar relative to browser window
+ var pos = this.progress_bar.getBoundingClientRect().left;
+
+ // Make sure event is reckonized cross-browser
+ event = event || window.event;
+
+ // Update progressbar
+ this.progress_bar.style.width = (event.clientX - pos) + "px";
+ };
+
+ // Eventlisteners for seeking
+ Video_player.prototype
+ .video_event_handler = function(videoPlayer, interval) {
+ // Update the progress bar
+ var animate_progress_bar = setInterval(function () {
+ videoPlayer.update_progress_bar();
+ }, interval);
+
+ // Fire when input value changes (user seeking)
+ videoPlayer.seek_bar
+ .addEventListener("change", function() {
+ videoPlayer.seeking();
+ });
+
+ // Fire when user clicks on seekbar
+ videoPlayer.seek_bar
+ .addEventListener("mousedown", function (clickEvent) {
+ // Pause video playback
+ videoPlayer.start_seek();
+
+ // Stop updating progressbar according to video progress
+ clearInterval(animate_progress_bar);
+
+ // Update progressbar to where user clicks
+ videoPlayer.handle_mouse_move(clickEvent);
+
+ // Bind progressbar to cursor
+ window.onmousemove = function(moveEvent){
+ videoPlayer.handle_mouse_move(moveEvent);
+ };
+ });
+
+ // Fire when user releases seekbar
+ videoPlayer.seek_bar
+ .addEventListener("mouseup", function () {
+
+ // Unbind progressbar from cursor
+ window.onmousemove = null;
+
+ // Start video playback
+ videoPlayer.stop_seek();
+
+ // Animate the progressbar
+ animate_progress_bar = setInterval(function () {
+ videoPlayer.update_progress_bar();
+ }, interval);
+ });
+ };
+
+
+ var videoPlayer = new Video_player('video_1');
+ videoPlayer.video_event_handler(videoPlayer, 17);
+}
+
+angular.module("umbraco").controller("Umbraco.Dashboard.FormsDashboardController", FormsController);
+
+function startupLatestEditsController($scope) {
+
+}
+angular.module("umbraco").controller("Umbraco.Dashboard.StartupLatestEditsController", startupLatestEditsController);
+
+function MediaFolderBrowserDashboardController($rootScope, $scope, $location, contentTypeResource, userService) {
+
+ var currentUser = {};
+
+ userService.getCurrentUser().then(function (user) {
+
+ currentUser = user;
+
+ // check if the user start node is the dashboard
+ if(currentUser.startMediaId === -1) {
+
+ //get the system media listview
+ contentTypeResource.getPropertyTypeScaffold(-96)
+ .then(function(dt) {
+
+ $scope.fakeProperty = {
+ alias: "contents",
+ config: dt.config,
+ description: "",
+ editor: dt.editor,
+ hideLabel: true,
+ id: 1,
+ label: "Contents:",
+ validation: {
+ mandatory: false,
+ pattern: null
+ },
+ value: "",
+ view: dt.view
+ };
+
+ });
+
+ } else {
+ // redirect to start node
+ $location.path("/media/media/edit/" + currentUser.startMediaId);
+ }
+
+ });
+
+}
+angular.module("umbraco").controller("Umbraco.Dashboard.MediaFolderBrowserDashboardController", MediaFolderBrowserDashboardController);
+
+function ExamineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) {
+
+ $scope.indexerDetails = [];
+ $scope.searcherDetails = [];
+ $scope.loading = true;
+
+ function checkProcessing(indexer, checkActionName) {
+ umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl",
+ checkActionName,
+ { indexerName: indexer.name })),
+ 'Failed to check index processing')
+ .then(function(data) {
+
+ if (data !== null && data !== "null") {
+
+ //copy all resulting properties
+ for (var k in data) {
+ indexer[k] = data[k];
+ }
+ indexer.isProcessing = false;
+ } else {
+ $timeout(function() {
+ //don't continue if we've tried 100 times
+ if (indexer.processingAttempts < 100) {
+ checkProcessing(indexer, checkActionName);
+ //add an attempt
+ indexer.processingAttempts++;
+ } else {
+ //we've exceeded 100 attempts, stop processing
+ indexer.isProcessing = false;
+ }
+ },
+ 1000);
+ }
+ });
+ }
+
+ $scope.search = function(searcher, e) {
+ if (e && e.keyCode !== 13) {
+ return;
+ }
+
+ umbRequestHelper.resourcePromise(
+ $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl",
+ "GetSearchResults",
+ {
+ searcherName: searcher.name,
+ query: encodeURIComponent(searcher.searchText),
+ queryType: searcher.searchType
+ })),
+ 'Failed to search')
+ .then(function(searchResults) {
+ searcher.isSearching = true;
+ searcher.searchResults = searchResults;
+ });
+ }
+
+ $scope.toggle = function(provider, propName) {
+ if (provider[propName] !== undefined) {
+ provider[propName] = !provider[propName];
+ } else {
+ provider[propName] = true;
+ }
+ }
+
+ $scope.rebuildIndex = function(indexer) {
+ if (confirm("This will cause the index to be rebuilt. " +
+ "Depending on how much content there is in your site this could take a while. " +
+ "It is not recommended to rebuild an index during times of high website traffic " +
+ "or when editors are editing content.")) {
+
+ indexer.isProcessing = true;
+ indexer.processingAttempts = 0;
+
+ umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl",
+ "PostRebuildIndex",
+ { indexerName: indexer.name })),
+ 'Failed to rebuild index')
+ .then(function() {
+
+ //rebuilding has started, nothing is returned accept a 200 status code.
+ //lets poll to see if it is done.
+ $timeout(function() {
+ checkProcessing(indexer, "PostCheckRebuildIndex");
+ },
+ 1000);
+
+ });
+ }
+ }
+
+ $scope.optimizeIndex = function(indexer) {
+ if (confirm("This will cause the index to be optimized which will improve its performance. " +
+ "It is not recommended to optimize an index during times of high website traffic " +
+ "or when editors are editing content.")) {
+ indexer.isProcessing = true;
+
+ umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl",
+ "PostOptimizeIndex",
+ { indexerName: indexer.name })),
+ 'Failed to optimize index')
+ .then(function() {
+
+ //optimizing has started, nothing is returned accept a 200 status code.
+ //lets poll to see if it is done.
+ $timeout(function() {
+ checkProcessing(indexer, "PostCheckOptimizeIndex");
+ },
+ 1000);
+
+ });
+ }
+ }
+
+ $scope.closeSearch = function(searcher) {
+ searcher.isSearching = true;
+ }
+
+ //go get the data
+
+ //combine two promises and execute when they are both done
+ $q.all([
+
+ //get the indexer details
+ umbRequestHelper.resourcePromise(
+ $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")),
+ 'Failed to retrieve indexer details')
+ .then(function(data) {
+ $scope.indexerDetails = data;
+ }),
+
+ //get the searcher details
+ umbRequestHelper.resourcePromise(
+ $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")),
+ 'Failed to retrieve searcher details')
+ .then(function(data) {
+ $scope.searcherDetails = data;
+ for (var s in $scope.searcherDetails) {
+ $scope.searcherDetails[s].searchType = "text";
+ }
+ })
+ ])
+ .then(function() {
+ //all init loading is complete
+ $scope.loading = false;
+ });
+}
+
+angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", ExamineMgmtController);
+(function() {
+ "use strict";
+
+ function HealthCheckController($scope, healthCheckResource) {
+ var SUCCESS = 0;
+ var WARNING = 1;
+ var ERROR = 2;
+ var INFO = 3;
+
+ var vm = this;
+
+ vm.viewState = "list";
+ vm.groups = [];
+ vm.selectedGroup = {};
+
+ vm.getStatus = getStatus;
+ vm.executeAction = executeAction;
+ vm.checkAllGroups = checkAllGroups;
+ vm.checkAllInGroup = checkAllInGroup;
+ vm.openGroup = openGroup;
+ vm.setViewState = setViewState;
+
+ // Get a (grouped) list of all health checks
+ healthCheckResource.getAllChecks()
+ .then(function(response) {
+ vm.groups = response;
+ });
+
+ function setGroupGlobalResultType(group) {
+ var totalSuccess = 0;
+ var totalError = 0;
+ var totalWarning = 0;
+ var totalInfo = 0;
+
+ // count total number of statusses
+ angular.forEach(group.checks,
+ function(check) {
+ angular.forEach(check.status,
+ function(status) {
+ switch (status.resultType) {
+ case SUCCESS:
+ totalSuccess = totalSuccess + 1;
+ break;
+ case WARNING:
+ totalWarning = totalWarning + 1;
+ break;
+ case ERROR:
+ totalError = totalError + 1;
+ break;
+ case INFO:
+ totalInfo = totalInfo + 1;
+ break;
+ }
+ });
+ });
+
+ group.totalSuccess = totalSuccess;
+ group.totalError = totalError;
+ group.totalWarning = totalWarning;
+ group.totalInfo = totalInfo;
+
+ }
+
+ // Get the status of an individual check
+ function getStatus(check) {
+ check.loading = true;
+ check.status = null;
+ healthCheckResource.getStatus(check.id)
+ .then(function(response) {
+ check.loading = false;
+ check.status = response;
+ });
+ }
+
+ function executeAction(check, index, action) {
+ check.loading = true;
+ healthCheckResource.executeAction(action)
+ .then(function(response) {
+ check.status[index] = response;
+ check.loading = false;
+ });
+ }
+
+ function checkAllGroups(groups) {
+ // set number of checks which has been executed
+ for (var i = 0; i < groups.length; i++) {
+ var group = groups[i];
+ checkAllInGroup(group, group.checks);
+ }
+ vm.groups = groups;
+ }
+
+ function checkAllInGroup(group, checks) {
+ group.checkCounter = 0;
+ group.loading = true;
+
+ angular.forEach(checks,
+ function(check) {
+
+ check.loading = true;
+
+ healthCheckResource.getStatus(check.id)
+ .then(function(response) {
+ check.status = response;
+ group.checkCounter = group.checkCounter + 1;
+ check.loading = false;
+
+ // when all checks are done, set global group result
+ if (group.checkCounter === checks.length) {
+ setGroupGlobalResultType(group);
+ group.loading = false;
+ }
+ });
+ });
+ }
+
+ function openGroup(group) {
+ vm.selectedGroup = group;
+ vm.viewState = "details";
+ }
+
+ function setViewState(state) {
+ vm.viewState = state;
+
+ if (state === 'list') {
+
+ for (var i = 0; i < vm.groups.length; i++) {
+ var group = vm.groups[i];
+ setGroupGlobalResultType(group);
+ }
+ }
+ }
+ }
+
+ angular.module("umbraco").controller("Umbraco.Dashboard.HealthCheckController", HealthCheckController);
+})();
+
+(function() {
+ "use strict";
+
+ function RedirectUrlsController($scope, redirectUrlsResource, notificationsService, localizationService, $q) {
+ //...todo
+ //search by url or url part
+ //search by domain
+ //display domain in dashboard results?
+
+ //used to cancel any request in progress if another one needs to take it's place
+ var vm = this;
+ var canceler = null;
+
+ vm.dashboard = {
+ searchTerm: "",
+ loading: false,
+ urlTrackerDisabled: false,
+ userIsAdmin: false
+ };
+
+ vm.pagination = {
+ pageIndex: 0,
+ pageNumber: 1,
+ totalPages: 1,
+ pageSize: 20
+ };
+
+ vm.goToPage = goToPage;
+ vm.search = search;
+ vm.removeRedirect = removeRedirect;
+ vm.disableUrlTracker = disableUrlTracker;
+ vm.enableUrlTracker = enableUrlTracker;
+ vm.filter = filter;
+ vm.checkEnabled = checkEnabled;
+
+ function activate() {
+ vm.checkEnabled().then(function() {
+ vm.search();
+ });
+ }
+
+ function checkEnabled() {
+ vm.dashboard.loading = true;
+ return redirectUrlsResource.getEnableState().then(function (response) {
+ vm.dashboard.urlTrackerDisabled = response.enabled !== true;
+ vm.dashboard.userIsAdmin = response.userIsAdmin;
+ vm.dashboard.loading = false;
+ });
+ }
+
+ function goToPage(pageNumber) {
+ vm.pagination.pageIndex = pageNumber - 1;
+ vm.pagination.pageNumber = pageNumber;
+ vm.search();
+ }
+
+ function search() {
+
+ vm.dashboard.loading = true;
+
+ var searchTerm = vm.dashboard.searchTerm;
+ if (searchTerm === undefined) {
+ searchTerm = "";
+ }
+
+ redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function(response) {
+
+ vm.redirectUrls = response.searchResults;
+
+ // update pagination
+ vm.pagination.pageIndex = response.currentPage;
+ vm.pagination.pageNumber = response.currentPage + 1;
+ vm.pagination.totalPages = response.pageCount;
+
+ vm.dashboard.loading = false;
+
+ });
+ }
+
+ function removeRedirect(redirectToDelete) {
+ localizationService.localize("redirectUrls_confirmRemove", [redirectToDelete.originalUrl, redirectToDelete.destinationUrl]).then(function (value) {
+ var toggleConfirm = confirm(value);
+
+ if (toggleConfirm) {
+ redirectUrlsResource.deleteRedirectUrl(redirectToDelete.redirectId).then(function () {
+
+ var index = vm.redirectUrls.indexOf(redirectToDelete);
+ vm.redirectUrls.splice(index, 1);
+ notificationsService.success(localizationService.localize("redirectUrls_redirectRemoved"));
+
+ // check if new redirects needs to be loaded
+ if (vm.redirectUrls.length === 0 && vm.pagination.totalPages > 1) {
+
+ // if we are not on the first page - get records from the previous
+ if (vm.pagination.pageIndex > 0) {
+ vm.pagination.pageIndex = vm.pagination.pageIndex - 1;
+ vm.pagination.pageNumber = vm.pagination.pageNumber - 1;
+ }
+
+ search();
+ }
+ }, function (error) {
+ notificationsService.error(localizationService.localize("redirectUrls_redirectRemoveError"));
+ });
+ }
+ });
+ }
+
+ function disableUrlTracker() {
+ localizationService.localize("redirectUrls_confirmDisable").then(function(value) {
+ var toggleConfirm = confirm(value);
+ if (toggleConfirm) {
+
+ redirectUrlsResource.toggleUrlTracker(true).then(function () {
+ activate();
+ notificationsService.success(localizationService.localize("redirectUrls_disabledConfirm"));
+ }, function (error) {
+ notificationsService.warning(localizationService.localize("redirectUrls_disableError"));
+ });
+
+ }
+ });
+ }
+
+ function enableUrlTracker() {
+ redirectUrlsResource.toggleUrlTracker(false).then(function() {
+ activate();
+ notificationsService.success(localizationService.localize("redirectUrls_enabledConfirm"));
+ }, function(error) {
+ notificationsService.warning(localizationService.localize("redirectUrls_enableError"));
+ });
+ }
+
+ var filterDebounced = _.debounce(function(e) {
+
+ $scope.$apply(function() {
+
+ //a canceler exists, so perform the cancelation operation and reset
+ if (canceler) {
+ canceler.resolve();
+ canceler = $q.defer();
+ } else {
+ canceler = $q.defer();
+ }
+
+ vm.search();
+
+ });
+
+ }, 200);
+
+ function filter() {
+ vm.dashboard.loading = true;
+ filterDebounced();
+ }
+
+ activate();
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", RedirectUrlsController);
+})();
+
+function XmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http) {
+
+ function check(item) {
+ var action = item.check;
+ umbRequestHelper.resourcePromise(
+ $http.get(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)),
+ 'Failed to retrieve data integrity status')
+ .then(function(result) {
+ item.checking = false;
+ item.invalid = result === "false";
+ });
+ }
+
+ $scope.fix = function(item) {
+ var action = item.fix;
+ if (item.fix) {
+ if (confirm("This will cause all xml structures for this type to be rebuilt. " +
+ "Depending on how much content there is in your site this could take a while. " +
+ "It is not recommended to rebuild xml structures if they are not out of sync, during times of high website traffic " +
+ "or when editors are editing content.")) {
+ item.fixing = true;
+ umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)),
+ 'Failed to retrieve data integrity status')
+ .then(function(result) {
+ item.fixing = false;
+ item.invalid = result === "false";
+ });
+ }
+ }
+ }
+
+ $scope.items = {
+ "contentXml": {
+ label: "Content in the cmsContentXml table",
+ checking: true,
+ fixing: false,
+ fix: "FixContentXmlTable",
+ check: "CheckContentXmlTable"
+ },
+ "mediaXml": {
+ label: "Media in the cmsContentXml table",
+ checking: true,
+ fixing: false,
+ fix: "FixMediaXmlTable",
+ check: "CheckMediaXmlTable"
+ },
+ "memberXml": {
+ label: "Members in the cmsContentXml table",
+ checking: true,
+ fixing: false,
+ fix: "FixMembersXmlTable",
+ check: "CheckMembersXmlTable"
+ }
+ };
+
+ for (var i in $scope.items) {
+ check($scope.items[i]);
+ }
+}
+
+angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", XmlDataIntegrityReportController);
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DataType.CreateController
+ * @function
+ *
+ * @description
+ * The controller for the data type creation dialog
+ */
+function DataTypeCreateController($scope, $location, navigationService, dataTypeResource, formHelper, appState) {
+
+ $scope.model = {
+ folderName: "",
+ creatingFolder: false
+ };
+
+ var node = $scope.dialogOptions.currentNode;
+
+ $scope.showCreateFolder = function() {
+ $scope.model.creatingFolder = true;
+ }
+
+ $scope.createContainer = function () {
+ if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) {
+ dataTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
+
+ navigationService.hideMenu();
+ var currPath = node.path ? node.path : "-1";
+ navigationService.syncTree({ tree: "datatypes", path: currPath + "," + folderId, forceReload: true, activate: true });
+
+ formHelper.resetForm({ scope: $scope });
+
+ var section = appState.getSectionState("currentSection");
+
+ }, function(err) {
+
+ //TODO: Handle errors
+ });
+ };
+ }
+
+ $scope.createDataType = function() {
+ $location.search('create', null);
+ $location.path("/developer/datatypes/edit/" + node.id).search("create", "true");
+ navigationService.hideMenu();
+ }
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.DataType.CreateController", DataTypeCreateController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.ContentDeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService) {
+
+ $scope.performDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ dataTypeResource.deleteById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ //TODO: Need to sync tree, etc...
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.performContainerDelete = function () {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ dataTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ //TODO: Need to sync tree, etc...
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.DataType.DeleteController", DataTypeDeleteController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DataType.EditController
+ * @function
+ *
+ * @description
+ * The controller for the content editor
+ */
+function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper, eventsService) {
+
+ //setup scope vars
+ $scope.page = {};
+ $scope.page.loading = false;
+ $scope.page.nameLocked = false;
+ $scope.page.menu = {};
+ $scope.page.menu.currentSection = appState.getSectionState("currentSection");
+ $scope.page.menu.currentNode = null;
+ var evts = [];
+
+ //method used to configure the pre-values when we retrieve them from the server
+ function createPreValueProps(preVals) {
+ $scope.preValues = [];
+ for (var i = 0; i < preVals.length; i++) {
+ $scope.preValues.push({
+ hideLabel: preVals[i].hideLabel,
+ alias: preVals[i].key,
+ description: preVals[i].description,
+ label: preVals[i].label,
+ view: preVals[i].view,
+ value: preVals[i].value
+ });
+ }
+ }
+
+ //set up the standard data type props
+ $scope.properties = {
+ selectedEditor: {
+ alias: "selectedEditor",
+ description: "Select a property editor",
+ label: "Property editor"
+ },
+ selectedEditorId: {
+ alias: "selectedEditorId",
+ label: "Property editor alias"
+ }
+ };
+
+ //setup the pre-values as props
+ $scope.preValues = [];
+
+ if ($routeParams.create) {
+
+ $scope.page.loading = true;
+
+ //we are creating so get an empty data type item
+ dataTypeResource.getScaffold($routeParams.id)
+ .then(function(data) {
+
+ $scope.preValuesLoaded = true;
+ $scope.content = data;
+
+ setHeaderNameState($scope.content);
+
+ //set a shared state
+ editorState.set($scope.content);
+
+ $scope.page.loading = false;
+
+ });
+ }
+ else {
+ loadDataType();
+ }
+
+ function loadDataType() {
+
+ $scope.page.loading = true;
+
+ //we are editing so get the content item from the server
+ dataTypeResource.getById($routeParams.id)
+ .then(function(data) {
+
+ $scope.preValuesLoaded = true;
+ $scope.content = data;
+
+ createPreValueProps($scope.content.preValues);
+
+ setHeaderNameState($scope.content);
+
+ //share state
+ editorState.set($scope.content);
+
+ //in one particular special case, after we've created a new item we redirect back to the edit
+ // route but there might be server validation errors in the collection which we need to display
+ // after the redirect, so we will bind all subscriptions which will show the server validation errors
+ // if there are any and then clear them so the collection no longer persists them.
+ serverValidationManager.executeAndClearAllSubscriptions();
+
+ navigationService.syncTree({ tree: "datatypes", path: data.path }).then(function (syncArgs) {
+ $scope.page.menu.currentNode = syncArgs.node;
+ });
+
+ $scope.page.loading = false;
+
+ });
+ }
+
+ $scope.$watch("content.selectedEditor", function (newVal, oldVal) {
+
+ //when the value changes, we need to dynamically load in the new editor
+ if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) {
+ //we are editing so get the content item from the server
+ var currDataTypeId = $routeParams.create ? undefined : $routeParams.id;
+ dataTypeResource.getPreValues(newVal, currDataTypeId)
+ .then(function (data) {
+ $scope.preValuesLoaded = true;
+ $scope.content.preValues = data;
+ createPreValueProps($scope.content.preValues);
+
+ setHeaderNameState($scope.content);
+
+ //share state
+ editorState.set($scope.content);
+ });
+ }
+ });
+
+ function setHeaderNameState(content) {
+
+ if(content.isSystem == 1) {
+ $scope.page.nameLocked = true;
+ }
+
+ }
+
+ $scope.save = function() {
+
+ if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
+
+ $scope.page.saveButtonState = "busy";
+
+ dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create)
+ .then(function(data) {
+
+ formHelper.resetForm({ scope: $scope, notifications: data.notifications });
+
+ contentEditingHelper.handleSuccessfulSave({
+ scope: $scope,
+ savedContent: data,
+ rebindCallback: function() {
+ createPreValueProps(data.preValues);
+ }
+ });
+
+ setHeaderNameState($scope.content);
+
+ //share state
+ editorState.set($scope.content);
+
+ navigationService.syncTree({ tree: "datatypes", path: data.path, forceReload: true }).then(function (syncArgs) {
+ $scope.page.menu.currentNode = syncArgs.node;
+ });
+
+ $scope.page.saveButtonState = "success";
+
+ dataTypeHelper.rebindChangedProperties($scope.content, data);
+
+ }, function(err) {
+
+ //NOTE: in the case of data type values we are setting the orig/new props
+ // to be the same thing since that only really matters for content/media.
+ contentEditingHelper.handleSaveError({
+ redirectOnFailure: false,
+ err: err
+ });
+
+ $scope.page.saveButtonState = "error";
+
+ //share state
+ editorState.set($scope.content);
+
+ dataTypeHelper.rebindChangedProperties($scope.content, data);
+ });
+ }
+
+ };
+
+ evts.push(eventsService.on("app.refreshEditor", function(name, error) {
+ loadDataType();
+ }));
+
+ //ensure to unregister from all events!
+ $scope.$on('$destroy', function () {
+ for (var e in evts) {
+ eventsService.unsubscribe(evts[e]);
+ }
+ });
+
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.DataType.EditController", DataTypeEditController);
+
+angular.module("umbraco")
+.controller("Umbraco.Editors.DataType.MoveController",
+ function ($scope, dataTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
+ var dialogOptions = $scope.dialogOptions;
+ $scope.dialogTreeEventHandler = $({});
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if ($scope.target) {
+ //un-select if there's a current one selected
+ $scope.target.selected = false;
+ }
+
+ $scope.target = args.node;
+ $scope.target.selected = true;
+ }
+
+ $scope.move = function () {
+
+ $scope.busy = true;
+ $scope.error = false;
+
+ dataTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id })
+ .then(function (path) {
+ $scope.error = false;
+ $scope.success = true;
+ $scope.busy = false;
+
+ //first we need to remove the node that launched the dialog
+ treeService.removeNode($scope.currentNode);
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the moved content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
+
+ navigationService.syncTree({ tree: "dataTypes", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "dataTypes", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ eventsService.emit('app.refreshEditor');
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ $scope.busy = false;
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+
+angular.module("umbraco")
+.controller("Umbraco.Editors.DocumentTypes.CopyController",
+ function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
+ var dialogOptions = $scope.dialogOptions;
+ $scope.dialogTreeEventHandler = $({});
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if ($scope.target) {
+ //un-select if there's a current one selected
+ $scope.target.selected = false;
+ }
+
+ $scope.target = args.node;
+ $scope.target.selected = true;
+ }
+
+ $scope.copy = function () {
+
+ $scope.busy = true;
+ $scope.error = false;
+
+ contentTypeResource.copy({ parentId: $scope.target.id, id: dialogOptions.currentNode.id })
+ .then(function (path) {
+ $scope.error = false;
+ $scope.success = true;
+ $scope.busy = false;
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the copied content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
+
+ navigationService.syncTree({ tree: "documentTypes", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "documentTypes", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ $scope.busy = false;
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.CreateController
+ * @function
+ *
+ * @description
+ * The controller for the doc type creation dialog
+ */
+function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState, notificationsService, localizationService) {
+
+ $scope.model = {
+ allowCreateFolder: $scope.dialogOptions.currentNode.parentId === null || $scope.dialogOptions.currentNode.nodeType === "container",
+ folderName: "",
+ creatingFolder: false,
+ };
+
+ var node = $scope.dialogOptions.currentNode,
+ localizeCreateFolder = localizationService.localize("defaultdialog_createFolder");
+
+ $scope.showCreateFolder = function() {
+ $scope.model.creatingFolder = true;
+ };
+
+ $scope.createContainer = function() {
+
+ if (formHelper.submitForm({scope: $scope, formCtrl: this.createFolderForm, statusMessage: localizeCreateFolder})) {
+
+ contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function(folderId) {
+
+ navigationService.hideMenu();
+
+ var currPath = node.path ? node.path : "-1";
+
+ navigationService.syncTree({
+ tree: "documenttypes",
+ path: currPath + "," + folderId,
+ forceReload: true,
+ activate: true
+ });
+
+ formHelper.resetForm({
+ scope: $scope
+ });
+
+ var section = appState.getSectionState("currentSection");
+
+ }, function(err) {
+
+ $scope.error = err;
+
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ }
+ };
+
+ $scope.createDocType = function() {
+ $location.search('create', null);
+ $location.search('notemplate', null);
+ $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true");
+ navigationService.hideMenu();
+ };
+
+ $scope.createComponent = function() {
+ $location.search('create', null);
+ $location.search('notemplate', null);
+ $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true");
+ navigationService.hideMenu();
+ };
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.DocumentTypes.CreateController", DocumentTypesCreateController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.DeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeResource, treeService, navigationService) {
+
+ $scope.performDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ contentTypeResource.deleteById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ //TODO: Need to sync tree, etc...
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.performContainerDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ //TODO: Need to sync tree, etc...
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.DeleteController", DocumentTypesDeleteController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.EditController
+ * @function
+ *
+ * @description
+ * The controller for the content type editor
+ */
+(function () {
+ "use strict";
+
+ function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) {
+
+ var vm = this;
+ var localizeSaving = localizationService.localize("general_saving");
+ var evts = [];
+
+ vm.save = save;
+
+ vm.currentNode = null;
+ vm.contentType = {};
+
+ vm.page = {};
+ vm.page.loading = false;
+ vm.page.saveButtonState = "init";
+ vm.page.navigation = [
+ {
+ "name": localizationService.localize("general_design"),
+ "icon": "icon-document-dashed-line",
+ "view": "views/documenttypes/views/design/design.html",
+ "active": true
+ },
+ {
+ "name": localizationService.localize("general_listView"),
+ "icon": "icon-list",
+ "view": "views/documenttypes/views/listview/listview.html"
+ },
+ {
+ "name": localizationService.localize("general_rights"),
+ "icon": "icon-keychain",
+ "view": "views/documenttypes/views/permissions/permissions.html"
+ },
+ {
+ "name": localizationService.localize("treeHeaders_templates"),
+ "icon": "icon-layout",
+ "view": "views/documenttypes/views/templates/templates.html"
+ }
+ ];
+
+ vm.page.keyboardShortcutsOverview = [
+ {
+ "name": localizationService.localize("main_sections"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_navigateSections"),
+ "keys": [{ "key": "1" }, { "key": "4" }],
+ "keyRange": true
+ }
+ ]
+ },
+ {
+ "name": localizationService.localize("general_design"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_addTab"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_addProperty"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_addEditor"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_editDataType"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }]
+ }
+ ]
+ },
+ {
+ "name": localizationService.localize("general_listView"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_toggleListView"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "l" }]
+ }
+ ]
+ },
+ {
+ "name": localizationService.localize("general_rights"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_toggleAllowAsRoot"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "r" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_addChildNode"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }]
+ }
+ ]
+ },
+ {
+ "name": localizationService.localize("treeHeaders_templates"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_addTemplate"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }]
+ }
+ ]
+ }
+ ];
+
+ contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
+ vm.page.modelsBuilder = result;
+ if (result) {
+ //Models builder mode:
+ vm.page.defaultButton = {
+ hotKey: "ctrl+s",
+ hotKeyWhenHidden: true,
+ labelKey: "buttons_save",
+ letter: "S",
+ type: "submit",
+ handler: function () { vm.save(); }
+ };
+ vm.page.subButtons = [{
+ hotKey: "ctrl+g",
+ hotKeyWhenHidden: true,
+ labelKey: "buttons_saveAndGenerateModels",
+ letter: "G",
+ handler: function () {
+
+ vm.page.saveButtonState = "busy";
+
+ vm.save().then(function (result) {
+
+ vm.page.saveButtonState = "busy";
+
+ localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) {
+ localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) {
+ notificationsService.info(headerValue, msgValue);
+ });
+ });
+
+ contentTypeHelper.generateModels().then(function (result) {
+
+ // generateModels() returns the dashboard content
+ if (!result.lastError) {
+
+ //re-check model status
+ contentTypeHelper.checkModelsBuilderStatus().then(function(statusResult) {
+ vm.page.modelsBuilder = statusResult;
+ });
+
+ //clear and add success
+ vm.page.saveButtonState = "init";
+ localizationService.localize("modelsBuilder_modelsGenerated").then(function(value) {
+ notificationsService.success(value);
+ });
+
+ } else {
+ vm.page.saveButtonState = "error";
+ localizationService.localize("modelsBuilder_modelsExceptionInUlog").then(function(value) {
+ notificationsService.error(value);
+ });
+ }
+
+ }, function () {
+ vm.page.saveButtonState = "error";
+ localizationService.localize("modelsBuilder_modelsGeneratedError").then(function(value) {
+ notificationsService.error(value);
+ });
+ });
+
+ });
+
+ }
+ }];
+ }
+ });
+
+ if ($routeParams.create) {
+ vm.page.loading = true;
+
+ //we are creating so get an empty data type item
+ contentTypeResource.getScaffold($routeParams.id)
+ .then(function (dt) {
+
+ init(dt);
+
+ vm.page.loading = false;
+
+ });
+ }
+ else {
+ loadDocumentType();
+ }
+
+ function loadDocumentType() {
+
+ vm.page.loading = true;
+
+ contentTypeResource.getById($routeParams.id).then(function (dt) {
+ init(dt);
+
+ syncTreeNode(vm.contentType, dt.path, true);
+
+ vm.page.loading = false;
+
+ });
+
+ }
+
+
+ /* ---------- SAVE ---------- */
+
+ function save() {
+
+ // only save if there is no overlays open
+ if(overlayHelper.getNumberOfOverlays() === 0) {
+
+ var deferred = $q.defer();
+
+ vm.page.saveButtonState = "busy";
+
+ // reformat allowed content types to array if id's
+ vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes);
+
+ contentEditingHelper.contentEditorPerformSave({
+ statusMessage: localizeSaving,
+ saveMethod: contentTypeResource.save,
+ scope: $scope,
+ content: vm.contentType,
+ //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
+ // type when server side validation fails - as opposed to content where we are capable of saving the content
+ // item if server side validation fails
+ redirectOnFailure: false,
+ // we need to rebind... the IDs that have been created!
+ rebindCallback: function (origContentType, savedContentType) {
+ vm.contentType.id = savedContentType.id;
+ vm.contentType.groups.forEach(function(group) {
+ if (!group.name) return;
+
+ var k = 0;
+ while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
+ k++;
+ if (k == savedContentType.groups.length) {
+ group.id = 0;
+ return;
+ }
+
+ var savedGroup = savedContentType.groups[k];
+ if (!group.id) group.id = savedGroup.id;
+
+ group.properties.forEach(function (property) {
+ if (property.id || !property.alias) return;
+
+ k = 0;
+ while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
+ k++;
+ if (k == savedGroup.properties.length) {
+ property.id = 0;
+ return;
+ }
+
+ var savedProperty = savedGroup.properties[k];
+ property.id = savedProperty.id;
+ });
+ });
+ }
+ }).then(function (data) {
+ //success
+ syncTreeNode(vm.contentType, data.path);
+
+ vm.page.saveButtonState = "success";
+
+ deferred.resolve(data);
+ }, function (err) {
+ //error
+ if (err) {
+ editorState.set($scope.content);
+ }
+ else {
+ localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
+ localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) {
+ notificationsService.error(headerValue, msgValue);
+ });
+ });
+ }
+ vm.page.saveButtonState = "error";
+
+ deferred.reject(err);
+ });
+ return deferred.promise;
+
+ }
+
+ }
+
+ function init(contentType) {
+
+ // set all tab to inactive
+ if (contentType.groups.length !== 0) {
+ angular.forEach(contentType.groups, function (group) {
+
+ angular.forEach(group.properties, function (property) {
+ // get data type details for each property
+ getDataTypeDetails(property);
+ });
+
+ });
+ }
+
+ // insert template on new doc types
+ if (!$routeParams.notemplate && contentType.id === 0) {
+ contentType.defaultTemplate = contentTypeHelper.insertDefaultTemplatePlaceholder(contentType.defaultTemplate);
+ contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates);
+ }
+
+ // convert icons for content type
+ convertLegacyIcons(contentType);
+
+ //set a shared state
+ editorState.set(contentType);
+
+ vm.contentType = contentType;
+ }
+
+ function convertLegacyIcons(contentType) {
+ // make array to store contentType icon
+ var contentTypeArray = [];
+
+ // push icon to array
+ contentTypeArray.push({ "icon": contentType.icon });
+
+ // run through icon method
+ iconHelper.formatContentTypeIcons(contentTypeArray);
+
+ // set icon back on contentType
+ contentType.icon = contentTypeArray[0].icon;
+ }
+
+ function getDataTypeDetails(property) {
+ if (property.propertyState !== "init") {
+
+ dataTypeResource.getById(property.dataTypeId)
+ .then(function (dataType) {
+ property.dataTypeIcon = dataType.icon;
+ property.dataTypeName = dataType.name;
+ });
+ }
+ }
+
+ /** Syncs the content type to it's tree node - this occurs on first load and after saving */
+ function syncTreeNode(dt, path, initialLoad) {
+ navigationService.syncTree({ tree: "documenttypes", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
+ vm.currentNode = syncArgs.node;
+ });
+ }
+
+ evts.push(eventsService.on("app.refreshEditor", function(name, error) {
+ loadDocumentType();
+ }));
+
+ //ensure to unregister from all events!
+ $scope.$on('$destroy', function () {
+ for (var e in evts) {
+ eventsService.unsubscribe(evts[e]);
+ }
+ });
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.EditController", DocumentTypesEditController);
+})();
+
+angular.module("umbraco")
+.controller("Umbraco.Editors.DocumentTypes.MoveController",
+ function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
+ var dialogOptions = $scope.dialogOptions;
+ $scope.dialogTreeEventHandler = $({});
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if ($scope.target) {
+ //un-select if there's a current one selected
+ $scope.target.selected = false;
+ }
+
+ $scope.target = args.node;
+ $scope.target.selected = true;
+ }
+
+ $scope.move = function () {
+
+ $scope.busy = true;
+ $scope.error = false;
+
+ contentTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id })
+ .then(function (path) {
+ $scope.error = false;
+ $scope.success = true;
+ $scope.busy = false;
+
+ //first we need to remove the node that launched the dialog
+ treeService.removeNode($scope.currentNode);
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the moved content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
+
+ navigationService.syncTree({ tree: "documentTypes", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "documentTypes", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ eventsService.emit('app.refreshEditor');
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ $scope.busy = false;
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.PropertyController
+ * @function
+ *
+ * @description
+ * The controller for the content type editor property dialog
+ */
+(function() {
+ 'use strict';
+
+ function PermissionsController($scope, contentTypeResource, iconHelper, contentTypeHelper, localizationService) {
+
+ /* ----------- SCOPE VARIABLES ----------- */
+
+ var vm = this;
+ var childNodeSelectorOverlayTitle = "";
+
+ vm.contentTypes = [];
+ vm.selectedChildren = [];
+
+ vm.overlayTitle = "";
+
+ vm.addChild = addChild;
+ vm.removeChild = removeChild;
+
+ /* ---------- INIT ---------- */
+
+ init();
+
+ function init() {
+
+ childNodeSelectorOverlayTitle = localizationService.localize("contentTypeEditor_chooseChildNode");
+
+ contentTypeResource.getAll().then(function(contentTypes){
+
+ vm.contentTypes = contentTypes;
+
+ // convert legacy icons
+ iconHelper.formatContentTypeIcons(vm.contentTypes);
+
+ vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.contentTypes);
+
+ if($scope.model.id === 0) {
+ contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
+ }
+
+ });
+
+ }
+
+ function addChild($event) {
+ vm.childNodeSelectorOverlay = {
+ view: "itempicker",
+ title: childNodeSelectorOverlayTitle,
+ availableItems: vm.contentTypes,
+ selectedItems: vm.selectedChildren,
+ event: $event,
+ show: true,
+ submit: function(model) {
+ vm.selectedChildren.push(model.selectedItem);
+ $scope.model.allowedContentTypes.push(model.selectedItem.id);
+ vm.childNodeSelectorOverlay.show = false;
+ vm.childNodeSelectorOverlay = null;
+ }
+ };
+ }
+
+ function removeChild(selectedChild, index) {
+ // remove from vm
+ vm.selectedChildren.splice(index, 1);
+
+ // remove from content type model
+ var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id);
+ $scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
+ }
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.DocumentType.PermissionsController", PermissionsController);
+})();
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.TemplatesController
+ * @function
+ *
+ * @description
+ * The controller for the content type editor templates sub view
+ */
+(function() {
+ 'use strict';
+
+ function TemplatesController($scope, entityResource, contentTypeHelper, $routeParams) {
+
+ /* ----------- SCOPE VARIABLES ----------- */
+
+ var vm = this;
+
+ vm.availableTemplates = [];
+ vm.updateTemplatePlaceholder = false;
+
+
+ /* ---------- INIT ---------- */
+
+ init();
+
+ function init() {
+
+ entityResource.getAll("Template").then(function(templates){
+
+ vm.availableTemplates = templates;
+
+ // update placeholder template information on new doc types
+ if (!$routeParams.notemplate && $scope.model.id === 0) {
+ vm.updateTemplatePlaceholder = true;
+ vm.availableTemplates = contentTypeHelper.insertTemplatePlaceholder(vm.availableTemplates);
+ }
+
+ });
+
+ }
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.DocumentType.TemplatesController", TemplatesController);
+})();
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Media.CreateController
+ * @function
+ *
+ * @description
+ * The controller for the media creation dialog
+ */
+function mediaCreateController($scope, $routeParams, mediaTypeResource, iconHelper) {
+
+ mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
+ $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
+ });
+
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.Media.CreateController", mediaCreateController);
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.ContentDeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) {
+
+ $scope.performDelete = function() {
+
+ // stop from firing again on double-click
+ if ($scope.busy) { return false; }
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ $scope.busy = true;
+
+ mediaResource.deleteById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ treeService.removeNode($scope.currentNode);
+
+ if (rootNode) {
+ //ensure the recycle bin has child nodes now
+ var recycleBin = treeService.getDescendantNode(rootNode, -21);
+ if (recycleBin) {
+ recycleBin.hasChildren = true;
+ }
+ }
+
+ //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
+ if (editorState.current && editorState.current.id == $scope.currentNode.id) {
+
+ //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent
+ var location = "/media";
+ if ($scope.currentNode.parentId.toString() !== "-1")
+ location = "/media/media/edit/" + $scope.currentNode.parentId;
+
+ $location.path(location);
+ }
+
+ navigationService.hideMenu();
+
+ }, function (err) {
+
+ $scope.currentNode.loading = false;
+ $scope.busy = false;
+
+ //check if response is ysod
+ if (err.status && err.status >= 500) {
+ dialogService.ysodDialog(err);
+ }
+
+ if (err.data && angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Media.DeleteController", MediaDeleteController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Media.EditController
+ * @function
+ *
+ * @description
+ * The controller for the media editor
+ */
+function mediaEditController($scope, $routeParams, appState, mediaResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
+
+ //setup scope vars
+ $scope.currentSection = appState.getSectionState("currentSection");
+ $scope.currentNode = null; //the editors affiliated node
+
+ $scope.page = {};
+ $scope.page.loading = false;
+ $scope.page.menu = {};
+ $scope.page.menu.currentSection = appState.getSectionState("currentSection");
+ $scope.page.menu.currentNode = null; //the editors affiliated node
+ $scope.page.listViewPath = null;
+ $scope.page.saveButtonState = "init";
+
+ /** Syncs the content item to it's tree node - this occurs on first load and after saving */
+ function syncTreeNode(content, path, initialLoad) {
+
+ if (!$scope.content.isChildOfListView) {
+ navigationService.syncTree({ tree: "media", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
+ $scope.page.menu.currentNode = syncArgs.node;
+ });
+ }
+ else if (initialLoad === true) {
+
+ //it's a child item, just sync the ui node to the parent
+ navigationService.syncTree({ tree: "media", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
+
+ //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node
+ // from the server so that we can load in the actions menu.
+ umbRequestHelper.resourcePromise(
+ $http.get(content.treeNodeUrl),
+ 'Failed to retrieve data for child node ' + content.id).then(function (node) {
+ $scope.page.menu.currentNode = node;
+ });
+ }
+ }
+
+ if ($routeParams.create) {
+
+ $scope.page.loading = true;
+
+ mediaResource.getScaffold($routeParams.id, $routeParams.doctype)
+ .then(function (data) {
+ $scope.content = data;
+
+ editorState.set($scope.content);
+
+ $scope.page.loading = false;
+
+ });
+ }
+ else {
+
+ $scope.page.loading = true;
+
+ mediaResource.getById($routeParams.id)
+ .then(function (data) {
+
+ $scope.content = data;
+
+ if (data.isChildOfListView && data.trashed === false) {
+ $scope.page.listViewPath = ($routeParams.page)
+ ? "/media/media/edit/" + data.parentId + "?page=" + $routeParams.page
+ : "/media/media/edit/" + data.parentId;
+ }
+
+ editorState.set($scope.content);
+
+ //in one particular special case, after we've created a new item we redirect back to the edit
+ // route but there might be server validation errors in the collection which we need to display
+ // after the redirect, so we will bind all subscriptions which will show the server validation errors
+ // if there are any and then clear them so the collection no longer persists them.
+ serverValidationManager.executeAndClearAllSubscriptions();
+
+ syncTreeNode($scope.content, data.path, true);
+
+ if ($scope.content.parentId && $scope.content.parentId != -1) {
+ //We fetch all ancestors of the node to generate the footer breadcrump navigation
+ entityResource.getAncestors($routeParams.id, "media")
+ .then(function (anc) {
+ $scope.ancestors = anc;
+ });
+ }
+
+ $scope.page.loading = false;
+
+ });
+ }
+
+ $scope.save = function () {
+
+ if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
+
+ $scope.busy = true;
+ $scope.page.saveButtonState = "busy";
+
+ mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles())
+ .then(function(data) {
+
+ formHelper.resetForm({ scope: $scope, notifications: data.notifications });
+
+ contentEditingHelper.handleSuccessfulSave({
+ scope: $scope,
+ savedContent: data,
+ rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
+ });
+
+ editorState.set($scope.content);
+ $scope.busy = false;
+
+ syncTreeNode($scope.content, data.path);
+
+ $scope.page.saveButtonState = "success";
+
+ }, function(err) {
+
+ contentEditingHelper.handleSaveError({
+ err: err,
+ redirectOnFailure: true,
+ rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
+ });
+
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+
+ editorState.set($scope.content);
+ $scope.busy = false;
+ $scope.page.saveButtonState = "error";
+
+ });
+ }else{
+ $scope.busy = false;
+ }
+
+ };
+}
+
+angular.module("umbraco")
+ .controller("Umbraco.Editors.Media.EditController", mediaEditController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Media.EmptyRecycleBinController
+ * @function
+ *
+ * @description
+ * The controller for deleting media
+ */
+function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService, notificationsService, $route) {
+
+ $scope.busy = false;
+
+ $scope.performDelete = function() {
+
+ //(used in the UI)
+ $scope.busy = true;
+ $scope.currentNode.loading = true;
+
+ mediaResource.emptyRecycleBin($scope.currentNode.id).then(function (result) {
+
+ $scope.busy = false;
+ $scope.currentNode.loading = false;
+
+ //show any notifications
+ if (angular.isArray(result.notifications)) {
+ for (var i = 0; i < result.notifications.length; i++) {
+ notificationsService.showNotification(result.notifications[i]);
+ }
+ }
+
+ treeService.removeChildNodes($scope.currentNode);
+ navigationService.hideMenu();
+
+ //reload the current view
+ $route.reload();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Media.EmptyRecycleBinController", MediaEmptyRecycleBinController);
+
+//used for the media picker dialog
+angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController",
+ function ($scope, eventsService, mediaResource, appState, treeService, navigationService) {
+ var dialogOptions = $scope.dialogOptions;
+
+ $scope.dialogTreeEventHandler = $({});
+ var node = dialogOptions.currentNode;
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ eventsService.emit("editors.media.moveController.select", args);
+
+ if ($scope.target) {
+ //un-select if there's a current one selected
+ $scope.target.selected = false;
+ }
+
+ $scope.target = args.node;
+ $scope.target.selected = true;
+ }
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+
+ $scope.move = function () {
+ mediaResource.move({ parentId: $scope.target.id, id: node.id })
+ .then(function (path) {
+ $scope.error = false;
+ $scope.success = true;
+
+ //first we need to remove the node that launched the dialog
+ treeService.removeNode($scope.currentNode);
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the moved content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
+
+ navigationService.syncTree({ tree: "media", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "media", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ });
+ };
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Content.MediaRecycleBinController
+ * @function
+ *
+ * @description
+ * Controls the recycle bin for media
+ *
+ */
+
+function MediaRecycleBinController($scope, $routeParams, mediaResource, navigationService, localizationService) {
+
+ //ensures the list view doesn't actually load until we query for the list view config
+ // for the section
+ $scope.page = {};
+ $scope.page.name = "Recycle Bin";
+ $scope.page.nameLocked = true;
+
+ //ensures the list view doesn't actually load until we query for the list view config
+ // for the section
+ $scope.listViewPath = null;
+
+ $routeParams.id = "-21";
+ mediaResource.getRecycleBin().then(function (result) {
+ //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a
+ // single property, so we'll extract that property (list view) and use it's data.
+ var listproperty = result.tabs[0].properties[0];
+
+ _.each(listproperty.config, function (val, key) {
+ $scope.model.config[key] = val;
+ });
+ $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
+ });
+
+ $scope.model = { config: { entityType: $routeParams.section, layouts: [] } };
+
+ // sync tree node
+ navigationService.syncTree({ tree: "media", path: ["-1", $routeParams.id], forceReload: false });
+
+ localizePageName();
+
+ function localizePageName() {
+
+ var pageName = "general_recycleBin";
+
+ localizationService.localize(pageName).then(function (value) {
+ $scope.page.name = value;
+ });
+
+ }
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.Media.RecycleBinController", MediaRecycleBinController);
+
+angular.module("umbraco")
+.controller("Umbraco.Editors.MediaTypes.CopyController",
+ function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
+ var dialogOptions = $scope.dialogOptions;
+ $scope.dialogTreeEventHandler = $({});
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if ($scope.target) {
+ //un-select if there's a current one selected
+ $scope.target.selected = false;
+ }
+
+ $scope.target = args.node;
+ $scope.target.selected = true;
+ }
+
+ $scope.copy = function () {
+
+ $scope.busy = true;
+ $scope.error = false;
+
+ mediaTypeResource.copy({ parentId: $scope.target.id, id: dialogOptions.currentNode.id })
+ .then(function (path) {
+ $scope.error = false;
+ $scope.success = true;
+ $scope.busy = false;
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the copied content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
+
+ navigationService.syncTree({ tree: "mediaTypes", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "mediaTypes", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ $scope.busy = false;
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.MediaType.CreateController
+ * @function
+ *
+ * @description
+ * The controller for the media type creation dialog
+ */
+function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) {
+
+ $scope.model = {
+ folderName: "",
+ creatingFolder: false
+ };
+
+ var node = $scope.dialogOptions.currentNode,
+ localizeCreateFolder = localizationService.localize("defaultdialog_createFolder");
+
+ $scope.showCreateFolder = function() {
+ $scope.model.creatingFolder = true;
+ }
+
+ $scope.createContainer = function () {
+ if (formHelper.submitForm({
+ scope: $scope,
+ formCtrl: this.createFolderForm,
+ statusMessage: localizeCreateFolder
+ })) {
+ mediaTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
+
+ navigationService.hideMenu();
+ var currPath = node.path ? node.path : "-1";
+ navigationService.syncTree({ tree: "mediatypes", path: currPath + "," + folderId, forceReload: true, activate: true });
+
+ formHelper.resetForm({ scope: $scope });
+
+ var section = appState.getSectionState("currentSection");
+
+ }, function(err) {
+
+ //TODO: Handle errors
+ });
+ };
+ }
+
+ $scope.createMediaType = function() {
+ $location.search('create', null);
+ $location.path("/settings/mediatypes/edit/" + node.id).search("create", "true");
+ navigationService.hideMenu();
+ }
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.MediaTypes.CreateController", MediaTypesCreateController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.MediaType.DeleteController
+ * @function
+ *
+ * @description
+ * The controller for the media type delete dialog
+ */
+function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, treeService, navigationService) {
+
+ $scope.performDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ mediaTypeResource.deleteById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ //TODO: Need to sync tree, etc...
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.performContainerDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ mediaTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ //TODO: Need to sync tree, etc...
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.MediaTypes.DeleteController", MediaTypesDeleteController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.MediaType.EditController
+ * @function
+ *
+ * @description
+ * The controller for the media type editor
+ */
+(function () {
+ "use strict";
+
+ function MediaTypesEditController($scope, $routeParams, mediaTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) {
+
+ var vm = this;
+ var localizeSaving = localizationService.localize("general_saving");
+ var evts = [];
+
+ vm.save = save;
+
+ vm.currentNode = null;
+ vm.contentType = {};
+ vm.page = {};
+ vm.page.loading = false;
+ vm.page.saveButtonState = "init";
+ vm.page.navigation = [
+ {
+ "name": localizationService.localize("general_design"),
+ "icon": "icon-document-dashed-line",
+ "view": "views/mediatypes/views/design/design.html",
+ "active": true
+ },
+ {
+ "name": localizationService.localize("general_listView"),
+ "icon": "icon-list",
+ "view": "views/mediatypes/views/listview/listview.html"
+ },
+ {
+ "name": localizationService.localize("general_rights"),
+ "icon": "icon-keychain",
+ "view": "views/mediatypes/views/permissions/permissions.html"
+ }
+ ];
+
+ vm.page.keyboardShortcutsOverview = [
+ {
+ "name": localizationService.localize("main_sections"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_navigateSections"),
+ "keys": [{ "key": "1" }, { "key": "3" }],
+ "keyRange": true
+ }
+ ]
+ },
+ {
+ "name": localizationService.localize("general_design"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_addTab"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_addProperty"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_addEditor"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_editDataType"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }]
+ }
+ ]
+ },
+ {
+ "name": localizationService.localize("general_listView"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_toggleListView"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "l" }]
+ }
+ ]
+ },
+ {
+ "name": localizationService.localize("general_rights"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_toggleAllowAsRoot"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "r" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_addChildNode"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }]
+ }
+ ]
+ }
+ ];
+
+ contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
+ vm.page.modelsBuilder = result;
+ if (result) {
+ //Models builder mode:
+ vm.page.defaultButton = {
+ hotKey: "ctrl+s",
+ hotKeyWhenHidden: true,
+ labelKey: "buttons_save",
+ letter: "S",
+ type: "submit",
+ handler: function () { vm.save(); }
+ };
+ vm.page.subButtons = [{
+ hotKey: "ctrl+g",
+ hotKeyWhenHidden: true,
+ labelKey: "buttons_saveAndGenerateModels",
+ letter: "G",
+ handler: function () {
+
+ vm.page.saveButtonState = "busy";
+
+ vm.save().then(function (result) {
+
+ vm.page.saveButtonState = "busy";
+
+ localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) {
+ localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) {
+ notificationsService.info(headerValue, msgValue);
+ });
+ });
+
+ contentTypeHelper.generateModels().then(function (result) {
+
+ if (result.success) {
+
+ //re-check model status
+ contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
+ vm.page.modelsBuilder = statusResult;
+ });
+
+ //clear and add success
+ vm.page.saveButtonState = "init";
+ localizationService.localize("modelsBuilder_modelsGenerated").then(function(value) {
+ notificationsService.success(value);
+ });
+
+ } else {
+ vm.page.saveButtonState = "error";
+ localizationService.localize("modelsBuilder_modelsExceptionInUlog").then(function(value) {
+ notificationsService.error(value);
+ });
+ }
+
+ }, function () {
+ vm.page.saveButtonState = "error";
+ localizationService.localize("modelsBuilder_modelsGeneratedError").then(function(value) {
+ notificationsService.error(value);
+ });
+ });
+
+ });
+
+ }
+ }];
+ }
+ });
+
+ if ($routeParams.create) {
+ vm.page.loading = true;
+
+ //we are creating so get an empty data type item
+ mediaTypeResource.getScaffold($routeParams.id)
+ .then(function(dt) {
+ init(dt);
+
+ vm.page.loading = false;
+ });
+ }
+ else {
+ loadMediaType();
+ }
+
+ function loadMediaType() {
+ vm.page.loading = true;
+
+ mediaTypeResource.getById($routeParams.id).then(function(dt) {
+ init(dt);
+
+ syncTreeNode(vm.contentType, dt.path, true);
+
+ vm.page.loading = false;
+ });
+ }
+
+ /* ---------- SAVE ---------- */
+
+ function save() {
+
+ // only save if there is no overlays open
+ if(overlayHelper.getNumberOfOverlays() === 0) {
+
+ var deferred = $q.defer();
+
+ vm.page.saveButtonState = "busy";
+
+ // reformat allowed content types to array if id's
+ vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes);
+
+ contentEditingHelper.contentEditorPerformSave({
+ statusMessage: localizeSaving,
+ saveMethod: mediaTypeResource.save,
+ scope: $scope,
+ content: vm.contentType,
+ //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
+ // type when server side validation fails - as opposed to content where we are capable of saving the content
+ // item if server side validation fails
+ redirectOnFailure: false,
+ // we need to rebind... the IDs that have been created!
+ rebindCallback: function (origContentType, savedContentType) {
+ vm.contentType.id = savedContentType.id;
+ vm.contentType.groups.forEach(function (group) {
+ if (!group.name) return;
+
+ var k = 0;
+ while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
+ k++;
+ if (k == savedContentType.groups.length) {
+ group.id = 0;
+ return;
+ }
+
+ var savedGroup = savedContentType.groups[k];
+ if (!group.id) group.id = savedGroup.id;
+
+ group.properties.forEach(function (property) {
+ if (property.id || !property.alias) return;
+
+ k = 0;
+ while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
+ k++;
+ if (k == savedGroup.properties.length) {
+ property.id = 0;
+ return;
+ }
+
+ var savedProperty = savedGroup.properties[k];
+ property.id = savedProperty.id;
+ });
+ });
+ }
+ }).then(function (data) {
+ //success
+ syncTreeNode(vm.contentType, data.path);
+
+ vm.page.saveButtonState = "success";
+
+ deferred.resolve(data);
+ }, function (err) {
+ //error
+ if (err) {
+ editorState.set($scope.content);
+ }
+ else {
+ localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
+ localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) {
+ notificationsService.error(headerValue, msgValue);
+ });
+ });
+ }
+
+ vm.page.saveButtonState = "error";
+
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ }
+ }
+
+ function init(contentType) {
+
+ // set all tab to inactive
+ if (contentType.groups.length !== 0) {
+ angular.forEach(contentType.groups, function (group) {
+
+ angular.forEach(group.properties, function (property) {
+ // get data type details for each property
+ getDataTypeDetails(property);
+ });
+
+ });
+ }
+
+ // convert icons for content type
+ convertLegacyIcons(contentType);
+
+ //set a shared state
+ editorState.set(contentType);
+
+ vm.contentType = contentType;
+ }
+
+ function convertLegacyIcons(contentType) {
+ // make array to store contentType icon
+ var contentTypeArray = [];
+
+ // push icon to array
+ contentTypeArray.push({ "icon": contentType.icon });
+
+ // run through icon method
+ iconHelper.formatContentTypeIcons(contentTypeArray);
+
+ // set icon back on contentType
+ contentType.icon = contentTypeArray[0].icon;
+ }
+
+ function getDataTypeDetails(property) {
+ if (property.propertyState !== "init") {
+
+ dataTypeResource.getById(property.dataTypeId)
+ .then(function(dataType) {
+ property.dataTypeIcon = dataType.icon;
+ property.dataTypeName = dataType.name;
+ });
+ }
+ }
+
+
+ /** Syncs the content type to it's tree node - this occurs on first load and after saving */
+ function syncTreeNode(dt, path, initialLoad) {
+ navigationService.syncTree({ tree: "mediatypes", path: path.split(","), forceReload: initialLoad !== true }).then(function(syncArgs) {
+ vm.currentNode = syncArgs.node;
+ });
+ }
+
+ evts.push(eventsService.on("app.refreshEditor", function(name, error) {
+ loadMediaType();
+ }));
+
+ //ensure to unregister from all events!
+ $scope.$on('$destroy', function () {
+ for (var e in evts) {
+ eventsService.unsubscribe(evts[e]);
+ }
+ });
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.MediaTypes.EditController", MediaTypesEditController);
+})();
+
+angular.module("umbraco")
+.controller("Umbraco.Editors.MediaTypes.MoveController",
+ function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
+
+ var dialogOptions = $scope.dialogOptions;
+ $scope.dialogTreeEventHandler = $({});
+
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if ($scope.target) {
+ //un-select if there's a current one selected
+ $scope.target.selected = false;
+ }
+
+ $scope.target = args.node;
+ $scope.target.selected = true;
+ }
+
+ $scope.move = function () {
+
+ $scope.busy = true;
+ $scope.error = false;
+
+ mediaTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id })
+ .then(function (path) {
+ $scope.error = false;
+ $scope.success = true;
+ $scope.busy = false;
+
+ //first we need to remove the node that launched the dialog
+ treeService.removeNode($scope.currentNode);
+
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+
+ //we need to do a double sync here: first sync to the moved content - but don't activate the node,
+ //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
+
+ navigationService.syncTree({ tree: "mediaTypes", path: path, forceReload: true, activate: false }).then(function (args) {
+ if (activeNode) {
+ var activeNodePath = treeService.getPath(activeNode).join();
+ //sync to this node now - depending on what was copied this might already be synced but might not be
+ navigationService.syncTree({ tree: "mediaTypes", path: activeNodePath, forceReload: false, activate: true });
+ }
+ });
+
+ eventsService.emit('app.refreshEditor');
+
+ }, function (err) {
+ $scope.success = false;
+ $scope.error = err;
+ $scope.busy = false;
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ });
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+ });
+
+(function() {
+ 'use strict';
+
+ function PermissionsController($scope, mediaTypeResource, iconHelper, contentTypeHelper, localizationService) {
+
+ /* ----------- SCOPE VARIABLES ----------- */
+
+ var vm = this;
+ var childNodeSelectorOverlayTitle = "";
+
+ vm.mediaTypes = [];
+ vm.selectedChildren = [];
+
+ vm.addChild = addChild;
+ vm.removeChild = removeChild;
+
+ /* ---------- INIT ---------- */
+
+ init();
+
+ function init() {
+
+ childNodeSelectorOverlayTitle = localizationService.localize("contentTypeEditor_chooseChildNode");
+
+ mediaTypeResource.getAll().then(function(mediaTypes){
+
+ vm.mediaTypes = mediaTypes;
+
+ // convert legacy icons
+ iconHelper.formatContentTypeIcons(vm.mediaTypes);
+
+ vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.mediaTypes);
+
+ if($scope.model.id === 0) {
+ contentTypeHelper.insertChildNodePlaceholder(vm.mediaTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
+ }
+
+ });
+
+ }
+
+ function addChild($event) {
+ vm.childNodeSelectorOverlay = {
+ view: "itempicker",
+ title: childNodeSelectorOverlayTitle,
+ availableItems: vm.mediaTypes,
+ selectedItems: vm.selectedChildren,
+ event: $event,
+ show: true,
+ submit: function(model) {
+ vm.selectedChildren.push(model.selectedItem);
+ $scope.model.allowedContentTypes.push(model.selectedItem.id);
+ vm.childNodeSelectorOverlay.show = false;
+ vm.childNodeSelectorOverlay = null;
+ }
+ };
+ }
+
+ function removeChild(selectedChild, index) {
+ // remove from vm
+ vm.selectedChildren.splice(index, 1);
+
+ // remove from content type model
+ var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id);
+ $scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
+ }
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.MediaType.PermissionsController", PermissionsController);
+})();
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Member.CreateController
+ * @function
+ *
+ * @description
+ * The controller for the member creation dialog
+ */
+function memberCreateController($scope, $routeParams, memberTypeResource, iconHelper) {
+
+ memberTypeResource.getTypes($scope.currentNode.id).then(function (data) {
+ $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
+ });
+
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.Member.CreateController", memberCreateController);
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Member.DeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function MemberDeleteController($scope, memberResource, treeService, navigationService, editorState, $location, $routeParams) {
+
+ $scope.performDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+
+ memberResource.deleteByKey($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ treeService.removeNode($scope.currentNode);
+
+ //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
+ if (editorState.current && editorState.current.key == $scope.currentNode.id) {
+ $location.path("/member/member/list/" + ($routeParams.listName ? $routeParams.listName : 'all-members'));
+ }
+
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Member.DeleteController", MemberDeleteController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Member.EditController
+ * @function
+ *
+ * @description
+ * The controller for the member editor
+ */
+function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
+
+ //setup scope vars
+ $scope.page = {};
+ $scope.page.loading = true;
+ $scope.page.menu = {};
+ $scope.page.menu.currentSection = appState.getSectionState("currentSection");
+ $scope.page.menu.currentNode = null; //the editors affiliated node
+ $scope.page.nameLocked = false;
+ $scope.page.listViewPath = null;
+ $scope.page.saveButtonState = "init";
+ $scope.busy = false;
+
+ $scope.page.listViewPath = ($routeParams.page && $routeParams.listName)
+ ? "/member/member/list/" + $routeParams.listName + "?page=" + $routeParams.page
+ : null;
+
+ //build a path to sync the tree with
+ function buildTreePath(data) {
+ return $routeParams.listName ? "-1," + $routeParams.listName : "-1";
+ }
+
+ if ($routeParams.create) {
+
+ //if there is no doc type specified then we are going to assume that
+ // we are not using the umbraco membership provider
+ if ($routeParams.doctype) {
+
+ //we are creating so get an empty member item
+ memberResource.getScaffold($routeParams.doctype)
+ .then(function(data) {
+
+ $scope.content = data;
+
+ setHeaderNameState($scope.content);
+
+ editorState.set($scope.content);
+
+ $scope.page.loading = false;
+
+ });
+ }
+ else {
+
+ memberResource.getScaffold()
+ .then(function (data) {
+ $scope.content = data;
+
+ setHeaderNameState($scope.content);
+
+ editorState.set($scope.content);
+
+ $scope.page.loading = false;
+
+ });
+ }
+
+ }
+ else {
+ //so, we usually refernce all editors with the Int ID, but with members we have
+ //a different pattern, adding a route-redirect here to handle this:
+ //isNumber doesnt work here since its seen as a string
+
+ //TODO: Why is this here - I don't understand why this would ever be an integer? This will not work when we support non-umbraco membership providers.
+
+ if ($routeParams.id && $routeParams.id.length < 9) {
+
+ entityResource.getById($routeParams.id, "Member").then(function(entity) {
+ $location.path("/member/member/edit/" + entity.key);
+ });
+ }
+ else {
+
+ //we are editing so get the content item from the server
+ memberResource.getByKey($routeParams.id)
+ .then(function(data) {
+
+ $scope.content = data;
+
+ setHeaderNameState($scope.content);
+
+ editorState.set($scope.content);
+
+ var path = buildTreePath(data);
+
+ //sync the tree (only for ui purposes)
+ navigationService.syncTree({ tree: "member", path: path.split(",") });
+
+ //it's the initial load of the editor, we need to get the tree node
+ // from the server so that we can load in the actions menu.
+ umbRequestHelper.resourcePromise(
+ $http.get(data.treeNodeUrl),
+ 'Failed to retrieve data for child node ' + data.key).then(function (node) {
+ $scope.page.menu.currentNode = node;
+ });
+
+ //in one particular special case, after we've created a new item we redirect back to the edit
+ // route but there might be server validation errors in the collection which we need to display
+ // after the redirect, so we will bind all subscriptions which will show the server validation errors
+ // if there are any and then clear them so the collection no longer persists them.
+ serverValidationManager.executeAndClearAllSubscriptions();
+
+ $scope.page.loading = false;
+
+ });
+ }
+
+ }
+
+ function setHeaderNameState(content) {
+
+ if(content.membershipScenario === 0) {
+ $scope.page.nameLocked = true;
+ }
+
+ }
+
+ $scope.save = function() {
+
+ if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
+
+ $scope.busy = true;
+ $scope.page.saveButtonState = "busy";
+
+ memberResource.save($scope.content, $routeParams.create, fileManager.getFiles())
+ .then(function(data) {
+
+ formHelper.resetForm({ scope: $scope, notifications: data.notifications });
+
+ contentEditingHelper.handleSuccessfulSave({
+ scope: $scope,
+ savedContent: data,
+ //specify a custom id to redirect to since we want to use the GUID
+ redirectId: data.key,
+ rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
+ });
+
+ editorState.set($scope.content);
+ $scope.busy = false;
+ $scope.page.saveButtonState = "success";
+
+ var path = buildTreePath(data);
+
+ //sync the tree (only for ui purposes)
+ navigationService.syncTree({ tree: "member", path: path.split(","), forceReload: true });
+
+ }, function (err) {
+
+ contentEditingHelper.handleSaveError({
+ redirectOnFailure: false,
+ err: err,
+ rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
+ });
+
+ editorState.set($scope.content);
+ $scope.busy = false;
+ $scope.page.saveButtonState = "error";
+
+ });
+ }else{
+ $scope.busy = false;
+ }
+
+ };
+
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Member.EditController", MemberEditController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Member.ListController
+ * @function
+ *
+ * @description
+ * The controller for the member list view
+ */
+function MemberListController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, localizationService) {
+
+ //setup scope vars
+ $scope.currentSection = appState.getSectionState("currentSection");
+ $scope.currentNode = null; //the editors affiliated node
+
+ $scope.page = {};
+ $scope.page.lockedName = true;
+ $scope.page.loading = true;
+
+ //we are editing so get the content item from the server
+ memberResource.getListNode($routeParams.id)
+ .then(function (data) {
+
+ $scope.content = data;
+
+ //translate "All Members"
+ if ($scope.content != null && $scope.content.name != null && $scope.content.name.replace(" ", "").toLowerCase() == "allmembers") {
+ localizationService.localize("member_allMembers").then(function (value) {
+ $scope.content.name = value;
+ });
+ }
+
+ editorState.set($scope.content);
+
+ navigationService.syncTree({ tree: "member", path: data.path.split(",") }).then(function (syncArgs) {
+ $scope.currentNode = syncArgs.node;
+ });
+
+ //in one particular special case, after we've created a new item we redirect back to the edit
+ // route but there might be server validation errors in the collection which we need to display
+ // after the redirect, so we will bind all subscriptions which will show the server validation errors
+ // if there are any and then clear them so the collection no longer persists them.
+ serverValidationManager.executeAndClearAllSubscriptions();
+
+ $scope.page.loading = false;
+
+ });
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Member.ListController", MemberListController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.MemberType.CreateController
+ * @function
+ *
+ * @description
+ * The controller for the member type creation dialog
+ */
+function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) {
+
+ $scope.model = {
+ folderName: "",
+ creatingFolder: false
+ };
+
+ var node = $scope.dialogOptions.currentNode,
+ localizeCreateFolder = localizationService.localize("defaultdialog_createFolder");
+
+
+ $scope.showCreateFolder = function() {
+ $scope.model.creatingFolder = true;
+ }
+
+ $scope.createContainer = function () {
+ if (formHelper.submitForm({
+ scope: $scope,
+ formCtrl: this.createFolderForm,
+ statusMessage: localizeCreateFolder
+ })) {
+ memberTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
+
+ navigationService.hideMenu();
+ var currPath = node.path ? node.path : "-1";
+ navigationService.syncTree({ tree: "membertypes", path: currPath + "," + folderId, forceReload: true, activate: true });
+
+ formHelper.resetForm({ scope: $scope });
+
+ var section = appState.getSectionState("currentSection");
+
+ }, function(err) {
+
+ //TODO: Handle errors
+ });
+ };
+ }
+
+ $scope.createMemberType = function() {
+ $location.search('create', null);
+ $location.path("/settings/membertypes/edit/" + node.id).search("create", "true");
+ navigationService.hideMenu();
+ }
+}
+
+angular.module('umbraco').controller("Umbraco.Editors.MemberTypes.CreateController", MemberTypesCreateController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.DeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function MemberTypesDeleteController($scope, memberTypeResource, treeService, navigationService) {
+
+ $scope.performDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ memberTypeResource.deleteById($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ //TODO: Need to sync tree, etc...
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.MemberTypes.DeleteController", MemberTypesDeleteController);
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.MemberType.EditController
+ * @function
+ *
+ * @description
+ * The controller for the member type editor
+ */
+(function () {
+ "use strict";
+
+ function MemberTypesEditController($scope, $rootScope, $routeParams, $log, $filter, memberTypeResource, dataTypeResource, editorState, iconHelper, formHelper, navigationService, contentEditingHelper, notificationsService, $q, localizationService, overlayHelper, contentTypeHelper) {
+
+ var vm = this;
+ var localizeSaving = localizationService.localize("general_saving");
+
+ vm.save = save;
+
+ vm.currentNode = null;
+ vm.contentType = {};
+ vm.page = {};
+ vm.page.loading = false;
+ vm.page.saveButtonState = "init";
+ vm.page.navigation = [
+ {
+ "name": localizationService.localize("general_design"),
+ "icon": "icon-document-dashed-line",
+ "view": "views/membertypes/views/design/design.html",
+ "active": true
+ }
+ ];
+
+ vm.page.keyboardShortcutsOverview = [
+ {
+ "name": localizationService.localize("shortcuts_shortcut"),
+ "shortcuts": [
+ {
+ "description": localizationService.localize("shortcuts_addTab"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_addProperty"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_addEditor"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }]
+ },
+ {
+ "description": localizationService.localize("shortcuts_editDataType"),
+ "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }]
+ }
+ ]
+ }
+ ];
+
+ contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
+ vm.page.modelsBuilder = result;
+ if (result) {
+ //Models builder mode:
+ vm.page.defaultButton = {
+ hotKey: "ctrl+s",
+ hotKeyWhenHidden: true,
+ labelKey: "buttons_save",
+ letter: "S",
+ type: "submit",
+ handler: function () { vm.save(); }
+ };
+ vm.page.subButtons = [{
+ hotKey: "ctrl+g",
+ hotKeyWhenHidden: true,
+ labelKey: "buttons_saveAndGenerateModels",
+ letter: "G",
+ handler: function () {
+
+ vm.page.saveButtonState = "busy";
+
+ vm.save().then(function (result) {
+
+ vm.page.saveButtonState = "busy";
+
+ localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) {
+ localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) {
+ notificationsService.info(headerValue, msgValue);
+ });
+ });
+
+ contentTypeHelper.generateModels().then(function (result) {
+
+ if (result.success) {
+
+ //re-check model status
+ contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
+ vm.page.modelsBuilder = statusResult;
+ });
+
+ //clear and add success
+ vm.page.saveButtonState = "init";
+ localizationService.localize("modelsBuilder_modelsGenerated").then(function(value) {
+ notificationsService.success(value);
+ });
+
+ } else {
+ vm.page.saveButtonState = "error";
+ localizationService.localize("modelsBuilder_modelsExceptionInUlog").then(function(value) {
+ notificationsService.error(value);
+ });
+ }
+
+ }, function () {
+ vm.page.saveButtonState = "error";
+ localizationService.localize("modelsBuilder_modelsGeneratedError").then(function(value) {
+ notificationsService.error(value);
+ });
+ });
+
+
+ });
+
+ }
+ }];
+ }
+ });
+
+ if ($routeParams.create) {
+
+ vm.page.loading = true;
+
+ //we are creating so get an empty data type item
+ memberTypeResource.getScaffold($routeParams.id)
+ .then(function (dt) {
+ init(dt);
+
+ vm.page.loading = false;
+ });
+ }
+ else {
+
+ vm.page.loading = true;
+
+ memberTypeResource.getById($routeParams.id).then(function (dt) {
+ init(dt);
+
+ syncTreeNode(vm.contentType, dt.path, true);
+
+ vm.page.loading = false;
+ });
+ }
+
+ function save() {
+ // only save if there is no overlays open
+ if(overlayHelper.getNumberOfOverlays() === 0) {
+
+ var deferred = $q.defer();
+
+ vm.page.saveButtonState = "busy";
+
+ contentEditingHelper.contentEditorPerformSave({
+ statusMessage: localizeSaving,
+ saveMethod: memberTypeResource.save,
+ scope: $scope,
+ content: vm.contentType,
+ //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
+ // type when server side validation fails - as opposed to content where we are capable of saving the content
+ // item if server side validation fails
+ redirectOnFailure: false,
+ // we need to rebind... the IDs that have been created!
+ rebindCallback: function (origContentType, savedContentType) {
+ vm.contentType.id = savedContentType.id;
+ vm.contentType.groups.forEach(function (group) {
+ if (!group.name) return;
+
+ var k = 0;
+ while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
+ k++;
+ if (k == savedContentType.groups.length) {
+ group.id = 0;
+ return;
+ }
+
+ var savedGroup = savedContentType.groups[k];
+ if (!group.id) group.id = savedGroup.id;
+
+ group.properties.forEach(function (property) {
+ if (property.id || !property.alias) return;
+
+ k = 0;
+ while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
+ k++;
+ if (k == savedGroup.properties.length) {
+ property.id = 0;
+ return;
+ }
+
+ var savedProperty = savedGroup.properties[k];
+ property.id = savedProperty.id;
+ });
+ });
+ }
+ }).then(function (data) {
+ //success
+ syncTreeNode(vm.contentType, data.path);
+
+ vm.page.saveButtonState = "success";
+
+ deferred.resolve(data);
+ }, function (err) {
+ //error
+ if (err) {
+ editorState.set($scope.content);
+ }
+ else {
+ localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
+ localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) {
+ notificationsService.error(headerValue, msgValue);
+ });
+ });
+ }
+
+ vm.page.saveButtonState = "error";
+
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ }
+
+ }
+
+ function init(contentType) {
+
+ // set all tab to inactive
+ if (contentType.groups.length !== 0) {
+ angular.forEach(contentType.groups, function (group) {
+
+ angular.forEach(group.properties, function (property) {
+ // get data type details for each property
+ getDataTypeDetails(property);
+ });
+
+ });
+ }
+
+ // convert legacy icons
+ convertLegacyIcons(contentType);
+
+ //set a shared state
+ editorState.set(contentType);
+
+ vm.contentType = contentType;
+
+ }
+
+ function convertLegacyIcons(contentType) {
+
+ // make array to store contentType icon
+ var contentTypeArray = [];
+
+ // push icon to array
+ contentTypeArray.push({ "icon": contentType.icon });
+
+ // run through icon method
+ iconHelper.formatContentTypeIcons(contentTypeArray);
+
+ // set icon back on contentType
+ contentType.icon = contentTypeArray[0].icon;
+
+ }
+
+ function getDataTypeDetails(property) {
+
+ if (property.propertyState !== "init") {
+
+ dataTypeResource.getById(property.dataTypeId)
+ .then(function (dataType) {
+ property.dataTypeIcon = dataType.icon;
+ property.dataTypeName = dataType.name;
+ });
+ }
+ }
+
+ /** Syncs the content type to it's tree node - this occurs on first load and after saving */
+ function syncTreeNode(dt, path, initialLoad) {
+
+ navigationService.syncTree({ tree: "membertypes", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
+ vm.currentNode = syncArgs.node;
+ });
+
+ }
+
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.MemberTypes.EditController", MemberTypesEditController);
+
+})();
+
+angular.module("umbraco")
+.controller("Umbraco.Editors.MemberTypes.MoveController",
+ function($scope){
+
+ });
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.Packages.DeleteController
+ * @function
+ *
+ * @description
+ * The controller for deleting content
+ */
+function PackageDeleteController($scope, packageResource, treeService, navigationService) {
+
+ $scope.performDelete = function() {
+
+ //mark it for deletion (used in the UI)
+ $scope.currentNode.loading = true;
+ packageResource.deleteCreatedPackage($scope.currentNode.id).then(function () {
+ $scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
+ treeService.removeNode($scope.currentNode);
+ navigationService.hideMenu();
+ });
+
+ };
+
+ $scope.cancel = function() {
+ navigationService.hideDialog();
+ };
+}
+
+angular.module("umbraco").controller("Umbraco.Editors.Packages.DeleteController", PackageDeleteController);
+
+(function () {
+ "use strict";
+
+ function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, localStorageService) {
+
+ //Hack!
+ // if there is a cookie value for packageInstallUri then we need to redirect there,
+ // the issue is that we still have webforms and we cannot go to a hash location and then window.reload
+ // because it will double load it.
+ // we will refresh and then navigate there.
+
+ var installPackageUri = localStorageService.get("packageInstallUri");
+ if (installPackageUri) {
+ localStorageService.remove("packageInstallUri");
+ }
+ if (installPackageUri && installPackageUri !== "installed") {
+ //navigate to the custom installer screen, if it is just "installed", then we'll
+ //show the installed view
+ $location.path(installPackageUri).search("");
+ }
+ else {
+ var vm = this;
+
+ vm.page = {};
+ vm.page.name = "Packages";
+ vm.page.navigation = [
+ {
+ "name": "Packages",
+ "icon": "icon-cloud",
+ "view": "views/packager/views/repo.html",
+ "active": !installPackageUri || installPackageUri === "navigation"
+ },
+ {
+ "name": "Installed",
+ "icon": "icon-box",
+ "view": "views/packager/views/installed.html",
+ "active": installPackageUri === "installed"
+ },
+ {
+ "name": "Install local",
+ "icon": "icon-add",
+ "view": "views/packager/views/install-local.html",
+ "active": installPackageUri === "local"
+ }
+ ];
+
+ $timeout(function () {
+ navigationService.syncTree({ tree: "packager", path: "-1" });
+ });
+ }
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.Packages.OverviewController", PackagesOverviewController);
+
+})();
+
+(function () {
+ "use strict";
+
+ function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, localStorageService, $timeout, $window, localizationService) {
+
+ var vm = this;
+ vm.state = "upload";
+
+ vm.localPackage = {};
+ vm.installPackage = installPackage;
+ vm.installState = {
+ status: "",
+ progress:0
+ };
+ vm.zipFile = {
+ uploadStatus: "idle",
+ uploadProgress: 0,
+ serverErrorMessage: null
+ };
+
+ $scope.handleFiles = function (files, event) {
+ if (files) {
+ for (var i = 0; i < files.length; i++) {
+ upload(files[i]);
+ }
+ }
+ };
+
+ function upload(file) {
+
+ Upload.upload({
+ url: umbRequestHelper.getApiUrl("packageInstallApiBaseUrl", "UploadLocalPackage"),
+ fields: {},
+ file: file
+ }).progress(function (evt) {
+
+ // calculate progress in percentage
+ var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10);
+
+ // set percentage property on file
+ vm.zipFile.uploadProgress = progressPercentage;
+
+ // set uploading status on file
+ vm.zipFile.uploadStatus = "uploading";
+
+ }).success(function (data, status, headers, config) {
+
+ if (data.notifications && data.notifications.length > 0) {
+
+ // set error status on file
+ vm.zipFile.uploadStatus = "error";
+
+ // Throw message back to user with the cause of the error
+ vm.zipFile.serverErrorMessage = data.notifications[0].message;
+
+ } else {
+
+ // set done status on file
+ vm.zipFile.uploadStatus = "done";
+ loadPackage();
+ vm.localPackage = data;
+ }
+
+ }).error(function (evt, status, headers, config) {
+
+ // set status done
+ vm.zipFile.uploadStatus = "error";
+
+ // If file not found, server will return a 404 and display this message
+ if (status === 404) {
+ vm.zipFile.serverErrorMessage = "File not found";
+ }
+ else if (status == 400) {
+ //it's a validation error
+ vm.zipFile.serverErrorMessage = evt.message;
+ }
+ else {
+ //it's an unhandled error
+ //if the service returns a detailed error
+ if (evt.InnerException) {
+ vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
+
+ //Check if its the common "too large file" exception
+ if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) {
+ vm.zipFile.serverErrorMessage = "File too large to upload";
+ }
+
+ } else if (evt.Message) {
+ file.serverErrorMessage = evt.Message;
+ }
+ }
+ });
+ }
+
+ function loadPackage() {
+ if (vm.zipFile.uploadStatus === "done") {
+ vm.state = "packageDetails";
+ }
+ }
+
+ function installPackage() {
+ vm.installState.status = localizationService.localize("packager_installStateImporting");
+ vm.installState.progress = "0";
+
+ packageResource
+ .import(vm.localPackage)
+ .then(function(pack) {
+ vm.installState.progress = "25";
+ vm.installState.status = localizationService.localize("packager_installStateInstalling");
+ vm.installState.progress = "50";
+ return packageResource.installFiles(pack);
+ },
+ installError)
+ .then(function(pack) {
+ vm.installState.status = localizationService.localize("packager_installStateRestarting");
+ vm.installState.progress = "75";
+ return packageResource.installData(pack);
+ },
+ installError)
+ .then(function(pack) {
+ vm.installState.status = localizationService.localize("packager_installStateComplete");
+ vm.installState.progress = "100";
+ return packageResource.cleanUp(pack);
+ },
+ installError)
+ .then(function(result) {
+
+ if (result.postInstallationPath) {
+ //Put the redirect Uri in a cookie so we can use after reloading
+ localStorageService.set("packageInstallUri", result.postInstallationPath);
+ }
+ else {
+ //set to a constant value so it knows to just go to the installed view
+ localStorageService.set("packageInstallUri", "installed");
+ }
+
+ //reload on next digest (after cookie)
+ $timeout(function () {
+ $window.location.reload(true);
+ });
+
+ },
+ installError);
+ }
+
+ function installError() {
+ //This will return a rejection meaning that the promise change above will stop
+ return $q.reject();
+ }
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.Packages.InstallLocalController", PackagesInstallLocalController);
+
+})();
+
+(function () {
+ "use strict";
+
+ function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) {
+
+ var vm = this;
+
+ vm.confirmUninstall = confirmUninstall;
+ vm.uninstallPackage = uninstallPackage;
+ vm.state = "list";
+ vm.installState = {
+ status: ""
+ };
+ vm.package = {};
+
+ function init() {
+ packageResource.getInstalled()
+ .then(function (packs) {
+ vm.installedPackages = packs;
+ });
+ vm.installState.status = "";
+ vm.state = "list";
+ }
+
+ function confirmUninstall(pck) {
+ vm.state = "packageDetails";
+ vm.package = pck;
+ }
+
+ function uninstallPackage(installedPackage) {
+ vm.installState.status = localizationService.localize("packager_installStateUninstalling");
+ vm.installState.progress = "0";
+
+ packageResource.uninstall(installedPackage.id)
+ .then(function () {
+
+ if (installedPackage.files.length > 0) {
+ vm.installState.status = localizationService.localize("packager_installStateComplete");
+ vm.installState.progress = "100";
+
+ //set this flag so that on refresh it shows the installed packages list
+ localStorageService.set("packageInstallUri", "installed");
+
+ //reload on next digest (after cookie)
+ $timeout(function () {
+ $window.location.reload(true);
+ });
+
+ }
+ else {
+ init();
+ }
+ });
+ }
+
+ init();
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.Packages.InstalledController", PackagesInstalledController);
+
+})();
+
+(function () {
+ "use strict";
+
+ function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, localStorageService, localizationService) {
+
+ var vm = this;
+
+ vm.packageViewState = "packageList";
+ vm.categories = [];
+ vm.loading = true;
+ vm.pagination = {
+ pageNumber: 1,
+ totalPages: 10,
+ pageSize: 24
+ };
+ vm.searchQuery = "";
+ vm.installState = {
+ status: "",
+ progress: 0,
+ type: "ok"
+ };
+ vm.selectCategory = selectCategory;
+ vm.showPackageDetails = showPackageDetails;
+ vm.setPackageViewState = setPackageViewState;
+ vm.nextPage = nextPage;
+ vm.prevPage = prevPage;
+ vm.goToPage = goToPage;
+ vm.installPackage = installPackage;
+ vm.downloadPackage = downloadPackage;
+ vm.openLightbox = openLightbox;
+ vm.closeLightbox = closeLightbox;
+ vm.search = search;
+
+ var currSort = "Latest";
+ //used to cancel any request in progress if another one needs to take it's place
+ var canceler = null;
+
+ function getActiveCategory() {
+ if (vm.searchQuery !== "") {
+ return "";
+ }
+ for (var i = 0; i < vm.categories.length; i++) {
+ if (vm.categories[i].active === true) {
+ return vm.categories[i].name;
+ }
+ }
+ return "";
+ }
+
+ function init() {
+
+ vm.loading = true;
+
+ $q.all([
+ ourPackageRepositoryResource.getCategories()
+ .then(function(cats) {
+ vm.categories = cats;
+ }),
+ ourPackageRepositoryResource.getPopular(8)
+ .then(function(pack) {
+ vm.popular = pack.packages;
+ }),
+ ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort)
+ .then(function(pack) {
+ vm.packages = pack.packages;
+ vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
+ })
+ ])
+ .then(function() {
+ vm.loading = false;
+ });
+
+ }
+
+ function selectCategory(selectedCategory, categories) {
+ var reset = false;
+ for (var i = 0; i < categories.length; i++) {
+ var category = categories[i];
+ if (category.name === selectedCategory.name && category.active === true) {
+ //it's already selected, let's unselect to show all again
+ reset = true;
+ }
+ category.active = false;
+ }
+
+ vm.loading = true;
+ vm.searchQuery = "";
+ var searchCategory = selectedCategory.name;
+ if (reset === true) {
+ searchCategory = "";
+ }
+
+ currSort = "Latest";
+
+ $q.all([
+ ourPackageRepositoryResource.getPopular(8, searchCategory)
+ .then(function(pack) {
+ vm.popular = pack.packages;
+ }),
+ ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery)
+ .then(function(pack) {
+ vm.packages = pack.packages;
+ vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
+ vm.pagination.pageNumber = 1;
+ })
+ ])
+ .then(function() {
+ vm.loading = false;
+ selectedCategory.active = reset === false;
+ });
+ }
+
+ function showPackageDetails(selectedPackage) {
+ ourPackageRepositoryResource.getDetails(selectedPackage.id)
+ .then(function (pack) {
+ packageResource.validateInstalled(pack.name, pack.latestVersion)
+ .then(function() {
+ //ok, can install
+ vm.package = pack;
+ vm.package.isValid = true;
+ vm.packageViewState = "packageDetails";
+ }, function() {
+ //nope, cannot install
+ vm.package = pack;
+ vm.package.isValid = false;
+ vm.packageViewState = "packageDetails";
+ })
+ });
+ }
+
+ function setPackageViewState(state) {
+ if(state) {
+ vm.packageViewState = state;
+ }
+ }
+
+ function nextPage(pageNumber) {
+ ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
+ .then(function (pack) {
+ vm.packages = pack.packages;
+ vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
+ });
+ }
+
+ function prevPage(pageNumber) {
+ ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
+ .then(function (pack) {
+ vm.packages = pack.packages;
+ vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
+ });
+ }
+
+ function goToPage(pageNumber) {
+ ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery)
+ .then(function (pack) {
+ vm.packages = pack.packages;
+ vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
+ });
+ }
+
+ function downloadPackage(selectedPackage) {
+ vm.loading = true;
+
+ packageResource
+ .fetch(selectedPackage.id)
+ .then(function(pack) {
+ vm.packageViewState = "packageInstall";
+ vm.loading = false;
+ vm.localPackage = pack;
+ vm.localPackage.allowed = true;
+ }, function (evt, status, headers, config) {
+
+ if (status == 400) {
+ //it's a validation error
+ vm.installState.type = "error";
+ vm.zipFile.serverErrorMessage = evt.message;
+ }
+ });
+ }
+
+ function error(e, args) {
+ //This will return a rejection meaning that the promise change above will stop
+ return $q.reject();
+ }
+
+ function installPackage(selectedPackage) {
+
+ vm.installState.status = localizationService.localize("packager_installStateImporting");
+ vm.installState.progress = "0";
+
+ packageResource
+ .import(selectedPackage)
+ .then(function(pack) {
+ vm.installState.status = localizationService.localize("packager_installStateInstalling");
+ vm.installState.progress = "33";
+ return packageResource.installFiles(pack);
+ },
+ error)
+ .then(function(pack) {
+ vm.installState.status = localizationService.localize("packager_installStateRestarting");
+ vm.installState.progress = "66";
+ return packageResource.installData(pack);
+ },
+ error)
+ .then(function(pack) {
+ vm.installState.status = localizationService.localize("packager_installStateComplete");
+ vm.installState.progress = "100";
+ return packageResource.cleanUp(pack);
+ },
+ error)
+ .then(function(result) {
+
+ if (result.postInstallationPath) {
+ //Put the redirect Uri in a cookie so we can use after reloading
+ localStorageService.set("packageInstallUri", result.postInstallationPath);
+ }
+
+ //reload on next digest (after cookie)
+ $timeout(function() {
+ window.location.reload(true);
+ });
+
+ },
+ error);
+ }
+
+ function openLightbox(itemIndex, items) {
+ vm.lightbox = {
+ show: true,
+ items: items,
+ activeIndex: itemIndex
+ };
+ }
+
+ function closeLightbox() {
+ vm.lightbox.show = false;
+ vm.lightbox = null;
+ }
+
+
+ var searchDebounced = _.debounce(function(e) {
+
+ $scope.$apply(function () {
+
+ //a canceler exists, so perform the cancelation operation and reset
+ if (canceler) {
+ canceler.resolve();
+ canceler = $q.defer();
+ }
+ else {
+ canceler = $q.defer();
+ }
+
+ currSort = vm.searchQuery ? "Default" : "Latest";
+
+ ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1,
+ vm.pagination.pageSize,
+ currSort,
+ "",
+ vm.searchQuery,
+ canceler)
+ .then(function(pack) {
+ vm.packages = pack.packages;
+ vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
+ vm.pagination.pageNumber = 1;
+ vm.loading = false;
+ //set back to null so it can be re-created
+ canceler = null;
+ });
+
+ });
+
+ }, 200);
+
+ function search(searchQuery) {
+ vm.loading = true;
+ searchDebounced();
+ }
+
+ init();
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.Editors.Packages.RepoController", PackagesRepoController);
+
+})();
+
+function imageFilePickerController($scope) {
+
+ $scope.pick = function() {
+ $scope.mediaPickerDialog = {};
+ $scope.mediaPickerDialog.view = "mediapicker";
+ $scope.mediaPickerDialog.show = true;
+
+ $scope.mediaPickerDialog.submit = function(model) {
+ $scope.model.value = model.selectedImages[0].image;
+ $scope.mediaPickerDialog.show = false;
+ $scope.mediaPickerDialog = null;
+ };
+
+ $scope.mediaPickerDialog.close = function(oldModel) {
+ $scope.mediaPickerDialog.show = false;
+ $scope.mediaPickerDialog = null;
+ };
+ };
+
+}
+
+angular.module('umbraco').controller("Umbraco.PrevalueEditors.ImageFilePickerController", imageFilePickerController);
+
+//this controller simply tells the dialogs service to open a mediaPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+function mediaPickerController($scope, dialogService, entityResource, $log, iconHelper) {
+
+ function trim(str, chr) {
+ var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
+ return str.replace(rgxtrim, '');
+ }
+
+ $scope.renderModel = [];
+
+ var dialogOptions = {
+ multiPicker: false,
+ entityType: "Media",
+ section: "media",
+ treeAlias: "media"
+ };
+
+ $scope.openContentPicker = function() {
+ $scope.contentPickerOverlay = dialogOptions;
+ $scope.contentPickerOverlay.view = "treePicker";
+ $scope.contentPickerOverlay.show = true;
+
+ $scope.contentPickerOverlay.submit = function(model) {
+
+ if ($scope.contentPickerOverlay.multiPicker) {
+ _.each(model.selection, function (item, i) {
+ $scope.add(item);
+ });
+ }
+ else {
+ $scope.clear();
+ $scope.add(model.selection[0]);
+ }
+
+ $scope.contentPickerOverlay.show = false;
+ $scope.contentPickerOverlay = null;
+ };
+
+ $scope.contentPickerOverlay.close = function(oldModel) {
+ $scope.contentPickerOverlay.show = false;
+ $scope.contentPickerOverlay = null;
+ };
+ }
+
+ $scope.remove =function(index, event){
+ event.preventDefault();
+ $scope.renderModel.splice(index, 1);
+ };
+
+ $scope.clear = function() {
+ $scope.renderModel = [];
+ };
+
+ $scope.add = function (item) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+ if (currIds.indexOf(item.id) < 0) {
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
+ }
+ };
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+ $scope.model.value = trim(currIds.join(), ",");
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+ //load media data
+ var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
+ entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) {
+ _.each(data, function (item, i) {
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
+ });
+ });
+
+}
+
+angular.module('umbraco').controller("Umbraco.PrevalueEditors.MediaPickerController",mediaPickerController);
+angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiValuesController",
+ function ($scope, $timeout) {
+
+ //NOTE: We need to make each item an object, not just a string because you cannot 2-way bind to a primitive.
+
+ $scope.newItem = "";
+ $scope.hasError = false;
+
+ if (!angular.isArray($scope.model.value)) {
+
+ //make an array from the dictionary
+ var items = [];
+ for (var i in $scope.model.value) {
+ items.push({
+ value: $scope.model.value[i].value,
+ sortOrder: $scope.model.value[i].sortOrder,
+ id: i
+ });
+ }
+
+ //ensure the items are sorted by the provided sort order
+ items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
+
+ //now make the editor model the array
+ $scope.model.value = items;
+ }
+
+ $scope.remove = function(item, evt) {
+ evt.preventDefault();
+
+ $scope.model.value = _.reject($scope.model.value, function (x) {
+ return x.value === item.value;
+ });
+
+ };
+
+ $scope.add = function (evt) {
+ evt.preventDefault();
+
+
+ if ($scope.newItem) {
+ if (!_.contains($scope.model.value, $scope.newItem)) {
+ $scope.model.value.push({ value: $scope.newItem });
+ $scope.newItem = "";
+ $scope.hasError = false;
+ return;
+ }
+ }
+
+ //there was an error, do the highlight (will be set back by the directive)
+ $scope.hasError = true;
+ };
+
+ $scope.sortableOptions = {
+ axis: 'y',
+ containment: 'parent',
+ cursor: 'move',
+ items: '> div.control-group',
+ tolerance: 'pointer',
+ update: function (e, ui) {
+ // Get the new and old index for the moved element (using the text as the identifier, so
+ // we'd have a problem if two prevalues were the same, but that would be unlikely)
+ var newIndex = ui.item.index();
+ var movedPrevalueText = $('input[type="text"]', ui.item).val();
+ var originalIndex = getElementIndexByPrevalueText(movedPrevalueText);
+
+ // Move the element in the model
+ if (originalIndex > -1) {
+ var movedElement = $scope.model.value[originalIndex];
+ $scope.model.value.splice(originalIndex, 1);
+ $scope.model.value.splice(newIndex, 0, movedElement);
+ }
+ }
+ };
+
+ function getElementIndexByPrevalueText(value) {
+ for (var i = 0; i < $scope.model.value.length; i++) {
+ if ($scope.model.value[i].value === value) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ });
+
+//this controller simply tells the dialogs service to open a mediaPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+angular.module('umbraco')
+.controller("Umbraco.PrevalueEditors.TreePickerController",
+
+ function($scope, dialogService, entityResource, $log, iconHelper){
+ $scope.renderModel = [];
+ $scope.ids = [];
+
+
+ var config = {
+ multiPicker: false,
+ entityType: "Document",
+ type: "content",
+ treeAlias: "content"
+ };
+
+ if($scope.model.value){
+ $scope.ids = $scope.model.value.split(',');
+ entityResource.getByIds($scope.ids, config.entityType).then(function (data) {
+ _.each(data, function (item, i) {
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
+ });
+ });
+ }
+
+ $scope.openContentPicker = function() {
+ $scope.treePickerOverlay = {};
+ $scope.treePickerOverlay.section = config.type;
+ $scope.treePickerOverlay.treeAlias = config.treeAlias;
+ $scope.treePickerOverlay.multiPicker = config.multiPicker;
+ $scope.treePickerOverlay.view = "treePicker";
+ $scope.treePickerOverlay.show = true;
+
+ $scope.treePickerOverlay.submit = function(model) {
+
+ if(config.multiPicker) {
+ populate(model.selection);
+ } else {
+ populate(model.selection[0]);
+ }
+
+ $scope.treePickerOverlay.show = false;
+ $scope.treePickerOverlay = null;
+ };
+
+ $scope.treePickerOverlay.close = function(oldModel) {
+ $scope.treePickerOverlay.show = false;
+ $scope.treePickerOverlay = null;
+ };
+
+ }
+
+ $scope.remove =function(index){
+ $scope.renderModel.splice(index, 1);
+ $scope.ids.splice(index, 1);
+ $scope.model.value = trim($scope.ids.join(), ",");
+ };
+
+ $scope.clear = function() {
+ $scope.model.value = "";
+ $scope.renderModel = [];
+ $scope.ids = [];
+ };
+
+ $scope.add =function(item){
+ if($scope.ids.indexOf(item.id) < 0){
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+
+ $scope.ids.push(item.id);
+ $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
+ $scope.model.value = trim($scope.ids.join(), ",");
+ }
+ };
+
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ $scope.model.value = trim($scope.ids.join(), ",");
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+ function trim(str, chr) {
+ var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
+ return str.replace(rgxtrim, '');
+ }
+
+ function populate(data){
+ if(angular.isArray(data)){
+ _.each(data, function (item, i) {
+ $scope.add(item);
+ });
+ }else{
+ $scope.clear();
+ $scope.add(data);
+ }
+ }
+});
+
+//this controller simply tells the dialogs service to open a mediaPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+angular.module('umbraco')
+.controller("Umbraco.PrevalueEditors.TreeSourceController",
+
+ function($scope, dialogService, entityResource, $log, iconHelper){
+
+ if (!$scope.model) {
+ $scope.model = {};
+ }
+ if (!$scope.model.value) {
+ $scope.model.value = {
+ type: "content"
+ };
+ }
+
+ if($scope.model.value.id && $scope.model.value.type !== "member"){
+ var ent = "Document";
+ if($scope.model.value.type === "media"){
+ ent = "Media";
+ }
+
+ entityResource.getById($scope.model.value.id, ent).then(function(item){
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ $scope.node = item;
+ });
+ }
+
+
+ $scope.openContentPicker =function(){
+ $scope.treePickerOverlay = {
+ view: "treepicker",
+ section: $scope.model.value.type,
+ treeAlias: $scope.model.value.type,
+ multiPicker: false,
+ show: true,
+ submit: function(model) {
+ var item = model.selection[0];
+ populate(item);
+ $scope.treePickerOverlay.show = false;
+ $scope.treePickerOverlay = null;
+ }
+ };
+ };
+
+ $scope.clear = function() {
+ $scope.model.value.id = undefined;
+ $scope.node = undefined;
+ $scope.model.value.query = undefined;
+ };
+
+
+ //we always need to ensure we dont submit anything broken
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ if($scope.model.value.type === "member"){
+ $scope.model.value.id = -1;
+ $scope.model.value.query = "";
+ }
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+ function populate(item){
+ $scope.clear();
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ $scope.node = item;
+ $scope.model.value.id = item.id;
+ }
+});
+function booleanEditorController($scope, $rootScope, assetsService) {
+
+ function setupViewModel() {
+ $scope.renderModel = {
+ value: false
+ };
+
+ if ($scope.model.config && $scope.model.config.default && $scope.model.config.default.toString() === "1" && $scope.model && !$scope.model.value) {
+ $scope.renderModel.value = true;
+ }
+
+ if ($scope.model && $scope.model.value && ($scope.model.value.toString() === "1" || angular.lowercase($scope.model.value) === "true")) {
+ $scope.renderModel.value = true;
+ }
+ }
+
+ setupViewModel();
+
+ $scope.$watch("renderModel.value", function (newVal) {
+ $scope.model.value = newVal === true ? "1" : "0";
+ });
+
+ //here we declare a special method which will be called whenever the value has changed from the server
+ //this is instead of doing a watch on the model.value = faster
+ $scope.model.onValueChanged = function (newVal, oldVal) {
+ //update the display val again if it has changed from the server
+ setupViewModel();
+ };
+
+}
+angular.module("umbraco").controller("Umbraco.PropertyEditors.BooleanController", booleanEditorController);
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordController",
+ function ($scope, $routeParams) {
+
+ function resetModel(isNew) {
+ //the model config will contain an object, if it does not we'll create defaults
+ //NOTE: We will not support doing the password regex on the client side because the regex on the server side
+ //based on the membership provider cannot always be ported to js from .net directly.
+ /*
+ {
+ hasPassword: true/false,
+ requiresQuestionAnswer: true/false,
+ enableReset: true/false,
+ enablePasswordRetrieval: true/false,
+ minPasswordLength: 10
+ }
+ */
+
+ //set defaults if they are not available
+ if (!$scope.model.config || $scope.model.config.disableToggle === undefined) {
+ $scope.model.config.disableToggle = false;
+ }
+ if (!$scope.model.config || $scope.model.config.hasPassword === undefined) {
+ $scope.model.config.hasPassword = false;
+ }
+ if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) {
+ $scope.model.config.enablePasswordRetrieval = true;
+ }
+ if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) {
+ $scope.model.config.requiresQuestionAnswer = false;
+ }
+ if (!$scope.model.config || $scope.model.config.enableReset === undefined) {
+ $scope.model.config.enableReset = true;
+ }
+ if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) {
+ $scope.model.config.minPasswordLength = 0;
+ }
+
+ //set the model defaults
+ if (!angular.isObject($scope.model.value)) {
+ //if it's not an object then just create a new one
+ $scope.model.value = {
+ newPassword: null,
+ oldPassword: null,
+ reset: null,
+ answer: null
+ };
+ }
+ else {
+ //just reset the values
+
+ if (!isNew) {
+ //if it is new, then leave the generated pass displayed
+ $scope.model.value.newPassword = null;
+ $scope.model.value.oldPassword = null;
+ }
+ $scope.model.value.reset = null;
+ $scope.model.value.answer = null;
+ }
+
+ //the value to compare to match passwords
+ if (!isNew) {
+ $scope.model.confirm = "";
+ }
+ else if ($scope.model.value.newPassword && $scope.model.value.newPassword.length > 0) {
+ //if it is new and a new password has been set, then set the confirm password too
+ $scope.model.confirm = $scope.model.value.newPassword;
+ }
+
+ }
+
+ resetModel($routeParams.create);
+
+ //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
+ //with validators turned on.
+ $scope.changing = $scope.model.config.disableToggle === true || !$scope.model.config.hasPassword;
+
+ //we're not currently changing so set the model to null
+ if (!$scope.changing) {
+ $scope.model.value = null;
+ }
+
+ $scope.doChange = function() {
+ resetModel();
+ $scope.changing = true;
+ //if there was a previously generated password displaying, clear it
+ $scope.model.value.generatedPassword = null;
+ };
+
+ $scope.cancelChange = function() {
+ $scope.changing = false;
+ //set model to null
+ $scope.model.value = null;
+ };
+
+ var unsubscribe = [];
+
+ //listen for the saved event, when that occurs we'll
+ //change to changing = false;
+ unsubscribe.push($scope.$on("formSubmitted", function() {
+ if ($scope.model.config.disableToggle === false) {
+ $scope.changing = false;
+ }
+ }));
+ unsubscribe.push($scope.$on("formSubmitting", function() {
+ //if there was a previously generated password displaying, clear it
+ if ($scope.changing && $scope.model.value) {
+ $scope.model.value.generatedPassword = null;
+ }
+ else if (!$scope.changing) {
+ //we are not changing, so the model needs to be null
+ $scope.model.value = null;
+ }
+ }));
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ for (var u in unsubscribe) {
+ unsubscribe[u]();
+ }
+ });
+
+ $scope.showReset = function() {
+ return $scope.model.config.hasPassword && $scope.model.config.enableReset;
+ };
+
+ $scope.showOldPass = function() {
+ return $scope.model.config.hasPassword &&
+ !$scope.model.config.allowManuallyChangingPassword &&
+ !$scope.model.config.enablePasswordRetrieval && !$scope.model.value.reset;
+ };
+
+ $scope.showNewPass = function () {
+ return !$scope.model.value.reset;
+ };
+
+ $scope.showConfirmPass = function() {
+ return !$scope.model.value.reset;
+ };
+
+ $scope.showCancelBtn = function() {
+ return $scope.model.config.disableToggle !== true && $scope.model.config.hasPassword;
+ };
+
+ });
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListController",
+ function($scope) {
+
+ if (angular.isObject($scope.model.config.items)) {
+
+ //now we need to format the items in the dictionary because we always want to have an array
+ var newItems = [];
+ var vals = _.values($scope.model.config.items);
+ var keys = _.keys($scope.model.config.items);
+ for (var i = 0; i < vals.length; i++) {
+ newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value });
+ }
+
+ //ensure the items are sorted by the provided sort order
+ newItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
+
+ //re-assign
+ $scope.model.config.items = newItems;
+
+ }
+
+ function setupViewModel() {
+ $scope.selectedItems = [];
+
+ //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
+ // to "" gets selected by default
+ if ($scope.model.value === null || $scope.model.value === undefined) {
+ $scope.model.value = [];
+ }
+
+ for (var i = 0; i < $scope.model.config.items.length; i++) {
+ var isChecked = _.contains($scope.model.value, $scope.model.config.items[i].id);
+ $scope.selectedItems.push({
+ checked: isChecked,
+ key: $scope.model.config.items[i].id,
+ val: $scope.model.config.items[i].value
+ });
+ }
+
+ }
+
+ setupViewModel();
+
+
+ //update the model when the items checked changes
+ $scope.$watch("selectedItems", function(newVal, oldVal) {
+
+ $scope.model.value = [];
+ for (var x = 0; x < $scope.selectedItems.length; x++) {
+ if ($scope.selectedItems[x].checked) {
+ $scope.model.value.push($scope.selectedItems[x].key);
+ }
+ }
+
+ }, true);
+
+ //here we declare a special method which will be called whenever the value has changed from the server
+ //this is instead of doing a watch on the model.value = faster
+ $scope.model.onValueChanged = function (newVal, oldVal) {
+ //update the display val again if it has changed from the server
+ setupViewModel();
+ };
+
+ });
+
+function ColorPickerController($scope) {
+ $scope.toggleItem = function (color) {
+ if ($scope.model.value == color) {
+ $scope.model.value = "";
+ //this is required to re-validate
+ $scope.propertyForm.modelValue.$setViewValue($scope.model.value);
+ }
+ else {
+ $scope.model.value = color;
+ //this is required to re-validate
+ $scope.propertyForm.modelValue.$setViewValue($scope.model.value);
+ }
+ };
+ // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
+ $scope.validateMandatory = function () {
+ return {
+ isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value != ""),
+ errorMsg: "Value cannot be empty",
+ errorKey: "required"
+ };
+ }
+ $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
+}
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.ColorPickerController", ColorPickerController);
+
+angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController",
+ function ($scope, $timeout, assetsService, angularHelper, $element) {
+ //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive.
+ var defaultColor = "000000";
+
+ $scope.newColor = defaultColor;
+ $scope.hasError = false;
+
+ assetsService.load([
+ //"lib/spectrum/tinycolor.js",
+ "lib/spectrum/spectrum.js"
+ ], $scope).then(function () {
+ var elem = $element.find("input");
+ elem.spectrum({
+ color: null,
+ showInitial: false,
+ chooseText: "choose", // TODO: These can be localised
+ cancelText: "cancel", // TODO: These can be localised
+ preferredFormat: "hex",
+ showInput: true,
+ clickoutFiresChange: true,
+ hide: function (color) {
+ //show the add butotn
+ $element.find(".btn.add").show();
+ },
+ change: function (color) {
+ angularHelper.safeApply($scope, function () {
+ $scope.newColor = color.toHexString().trimStart("#"); // #ff0000
+ });
+ },
+ show: function() {
+ //hide the add butotn
+ $element.find(".btn.add").hide();
+ }
+ });
+ });
+
+ if (!angular.isArray($scope.model.value)) {
+ //make an array from the dictionary
+ var items = [];
+ for (var i in $scope.model.value) {
+ items.push({
+ value: $scope.model.value[i],
+ id: i
+ });
+ }
+ //now make the editor model the array
+ $scope.model.value = items;
+ }
+
+ $scope.remove = function (item, evt) {
+
+ evt.preventDefault();
+
+ $scope.model.value = _.reject($scope.model.value, function (x) {
+ return x.value === item.value;
+ });
+
+ };
+
+ $scope.add = function (evt) {
+
+ evt.preventDefault();
+
+ if ($scope.newColor) {
+ var exists = _.find($scope.model.value, function(item) {
+ return item.value.toUpperCase() == $scope.newColor.toUpperCase();
+ });
+ if (!exists) {
+ $scope.model.value.push({ value: $scope.newColor });
+ //$scope.newColor = defaultColor;
+ // set colorpicker to default color
+ //var elem = $element.find("input");
+ //elem.spectrum("set", $scope.newColor);
+ $scope.hasError = false;
+ return;
+ }
+
+ //there was an error, do the highlight (will be set back by the directive)
+ $scope.hasError = true;
+ }
+
+ };
+
+ //load the separate css for the editor to avoid it blocking our js loading
+ assetsService.loadCss("lib/spectrum/spectrum.css");
+ });
+
+//this controller simply tells the dialogs service to open a mediaPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+
+function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) {
+
+ function trim(str, chr) {
+ var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
+ return str.replace(rgxtrim, '');
+ }
+
+ function startWatch() {
+ //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
+ // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
+ // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
+ // In their source code there is no event so we need to just subscribe to our model changes here.
+ //This also makes it easier to manage models, we update one and the rest will just work.
+ $scope.$watch(function () {
+ //return the joined Ids as a string to watch
+ return _.map($scope.renderModel, function (i) {
+ return i.id;
+ }).join();
+ }, function (newVal) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+ $scope.model.value = trim(currIds.join(), ",");
+
+ //Validate!
+ if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
+ $scope.contentPickerForm.minCount.$setValidity("minCount", false);
+ }
+ else {
+ $scope.contentPickerForm.minCount.$setValidity("minCount", true);
+ }
+
+ if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
+ $scope.contentPickerForm.maxCount.$setValidity("maxCount", false);
+ }
+ else {
+ $scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
+ }
+ });
+ }
+
+ $scope.renderModel = [];
+
+ $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
+
+ //the default pre-values
+ var defaultConfig = {
+ multiPicker: false,
+ showOpenButton: false,
+ showEditButton: false,
+ showPathOnHover: false,
+ startNode: {
+ query: "",
+ type: "content",
+ id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker
+ }
+ };
+
+ if ($scope.model.config) {
+ //merge the server config on top of the default config, then set the server config to use the result
+ $scope.model.config = angular.extend(defaultConfig, $scope.model.config);
+ }
+
+ //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
+ $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false);
+ $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false);
+ $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false);
+ $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false);
+
+ var entityType = $scope.model.config.startNode.type === "member"
+ ? "Member"
+ : $scope.model.config.startNode.type === "media"
+ ? "Media"
+ : "Document";
+ $scope.allowOpenButton = entityType === "Document" || entityType === "Media";
+ $scope.allowEditButton = entityType === "Document";
+
+ //the dialog options for the picker
+ var dialogOptions = {
+ multiPicker: $scope.model.config.multiPicker,
+ entityType: entityType,
+ filterCssClass: "not-allowed not-published",
+ startNodeId: null,
+ callback: function (data) {
+ if (angular.isArray(data)) {
+ _.each(data, function (item, i) {
+ $scope.add(item);
+ });
+ } else {
+ $scope.clear();
+ $scope.add(data);
+ }
+ angularHelper.getCurrentForm($scope).$setDirty();
+ },
+ treeAlias: $scope.model.config.startNode.type,
+ section: $scope.model.config.startNode.type
+ };
+
+ //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
+ // pre-value config on to the dialog options
+ angular.extend(dialogOptions, $scope.model.config);
+
+ //We need to manually handle the filter for members here since the tree displayed is different and only contains
+ // searchable list views
+ if (entityType === "Member") {
+ //first change the not allowed filter css class
+ dialogOptions.filterCssClass = "not-allowed";
+ var currFilter = dialogOptions.filter;
+ //now change the filter to be a method
+ dialogOptions.filter = function(i) {
+ //filter out the list view nodes
+ if (i.metaData.isContainer) {
+ return true;
+ }
+ if (!currFilter) {
+ return false;
+ }
+ //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller,
+ // but not much we can do about that since members require special filtering.
+ var filterItem = currFilter.toLowerCase().split(',');
+ var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0;
+ if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+
+ //if we have a query for the startnode, we will use that.
+ if ($scope.model.config.startNode.query) {
+ var rootId = $routeParams.id;
+ entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) {
+ dialogOptions.startNodeId = ent.id;
+ });
+ } else {
+ dialogOptions.startNodeId = $scope.model.config.startNode.id;
+ }
+
+ //dialog
+ $scope.openContentPicker = function() {
+ $scope.contentPickerOverlay = dialogOptions;
+ $scope.contentPickerOverlay.view = "treepicker";
+ $scope.contentPickerOverlay.show = true;
+
+ $scope.contentPickerOverlay.submit = function(model) {
+
+ if (angular.isArray(model.selection)) {
+ _.each(model.selection, function (item, i) {
+ $scope.add(item);
+ });
+ }
+
+ $scope.contentPickerOverlay.show = false;
+ $scope.contentPickerOverlay = null;
+ }
+
+ $scope.contentPickerOverlay.close = function(oldModel) {
+ $scope.contentPickerOverlay.show = false;
+ $scope.contentPickerOverlay = null;
+ }
+
+ };
+
+ $scope.remove = function (index) {
+ $scope.renderModel.splice(index, 1);
+ angularHelper.getCurrentForm($scope).$setDirty();
+ };
+
+ $scope.showNode = function (index) {
+ var item = $scope.renderModel[index];
+ var id = item.id;
+ var section = $scope.model.config.startNode.type.toLowerCase();
+
+ entityResource.getPath(id, entityType).then(function (path) {
+ navigationService.changeSection(section);
+ navigationService.showTree(section, {
+ tree: section, path: path, forceReload: false, activate: true
+ });
+ var routePath = section + "/" + section + "/edit/" + id.toString();
+ $location.path(routePath).search("");
+ });
+ }
+
+ $scope.add = function (item) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+
+ if (currIds.indexOf(item.id) < 0) {
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path });
+ }
+ };
+
+ $scope.clear = function () {
+ $scope.renderModel = [];
+ };
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+ $scope.model.value = trim(currIds.join(), ",");
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+ //load current data
+ var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
+ entityResource.getByIds(modelIds, entityType).then(function (data) {
+
+ //Ensure we populate the render model in the same order that the ids were stored!
+ _.each(modelIds, function (id, i) {
+ var entity = _.find(data, function (d) {
+ return d.id == id;
+ });
+
+ if (entity) {
+ entity.icon = iconHelper.convertFromLegacyIcon(entity.icon);
+ $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path });
+ }
+
+
+ });
+
+ //everything is loaded, start the watch on the model
+ startWatch();
+
+ });
+}
+
+angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController);
+
+function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) {
+
+ //setup the default config
+ var config = {
+ pickDate: true,
+ pickTime: true,
+ useSeconds: true,
+ format: "YYYY-MM-DD HH:mm:ss",
+ icons: {
+ time: "icon-time",
+ date: "icon-calendar",
+ up: "icon-chevron-up",
+ down: "icon-chevron-down"
+ }
+
+ };
+
+ //map the user config
+ $scope.model.config = angular.extend(config, $scope.model.config);
+ //ensure the format doesn't get overwritten with an empty string
+ if ($scope.model.config.format === "" || $scope.model.config.format === undefined || $scope.model.config.format === null) {
+ $scope.model.config.format = $scope.model.config.pickTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD";
+ }
+
+
+
+ $scope.hasDatetimePickerValue = $scope.model.value ? true : false;
+ $scope.datetimePickerValue = null;
+
+ //hide picker if clicking on the document
+ $scope.hidePicker = function () {
+ //$element.find("div:first").datetimepicker("hide");
+ // Sometimes the statement above fails and generates errors in the browser console. The following statements fix that.
+ var dtp = $element.find("div:first");
+ if (dtp && dtp.datetimepicker) {
+ dtp.datetimepicker("hide");
+ }
+ };
+ $(document).bind("click", $scope.hidePicker);
+
+ //handles the date changing via the api
+ function applyDate(e) {
+ angularHelper.safeApply($scope, function() {
+ // when a date is changed, update the model
+ if (e.date && e.date.isValid()) {
+ $scope.datePickerForm.datepicker.$setValidity("pickerError", true);
+ $scope.hasDatetimePickerValue = true;
+ $scope.datetimePickerValue = e.date.format($scope.model.config.format);
+ }
+ else {
+ $scope.hasDatetimePickerValue = false;
+ $scope.datetimePickerValue = null;
+ }
+
+ setModelValue();
+
+ if (!$scope.model.config.pickTime) {
+ $element.find("div:first").datetimepicker("hide", 0);
+ }
+ });
+ }
+
+ //sets the scope model value accordingly - this is the value to be sent up to the server and depends on
+ // if the picker is configured to offset time. We always format the date/time in a specific format for sending
+ // to the server, this is different from the format used to display the date/time.
+ function setModelValue() {
+ if ($scope.hasDatetimePickerValue) {
+ var elementData = $element.find("div:first").data().DateTimePicker;
+ if ($scope.model.config.pickTime) {
+ //check if we are supposed to offset the time
+ if ($scope.model.value && $scope.model.config.offsetTime === "1" && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
+ $scope.model.value = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
+ $scope.serverTime = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z");
+ }
+ else {
+ $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss");
+ }
+ }
+ else {
+ $scope.model.value = elementData.getDate().format("YYYY-MM-DD");
+ }
+ }
+ else {
+ $scope.model.value = null;
+ }
+ }
+
+ var picker = null;
+
+ $scope.clearDate = function() {
+ $scope.hasDatetimePickerValue = false;
+ $scope.datetimePickerValue = null;
+ $scope.model.value = null;
+ $scope.datePickerForm.datepicker.$setValidity("pickerError", true);
+ }
+
+ $scope.serverTime = null;
+ $scope.serverTimeNeedsOffsetting = false;
+ if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
+ // Will return something like 120
+ var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
+
+ // Will return something like -120
+ var localOffset = new Date().getTimezoneOffset();
+
+ // If these aren't equal then offsetting is needed
+ // note the minus in front of serverOffset needed
+ // because C# and javascript return the inverse offset
+ $scope.serverTimeNeedsOffsetting = (-serverOffset !== localOffset);
+ }
+
+ //get the current user to see if we can localize this picker
+ userService.getCurrentUser().then(function (user) {
+
+ assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css').then(function() {
+
+ var filesToLoad = ["lib/moment/moment-with-locales.js",
+ "lib/datetimepicker/bootstrap-datetimepicker.js"];
+
+
+ $scope.model.config.language = user.locale;
+
+
+ assetsService.load(filesToLoad, $scope).then(
+ function () {
+ //The Datepicker js and css files are available and all components are ready to use.
+
+ // Get the id of the datepicker button that was clicked
+ var pickerId = $scope.model.alias;
+
+ var element = $element.find("div:first");
+
+ // Open the datepicker and add a changeDate eventlistener
+ element
+ .datetimepicker(angular.extend({ useCurrent: true }, $scope.model.config))
+ .on("dp.change", applyDate)
+ .on("dp.error", function(a, b, c) {
+ $scope.hasDatetimePickerValue = false;
+ $scope.datePickerForm.datepicker.$setValidity("pickerError", false);
+ });
+
+ if ($scope.hasDatetimePickerValue) {
+ var dateVal;
+ //check if we are supposed to offset the time
+ if ($scope.model.value && $scope.model.config.offsetTime === "1" && $scope.serverTimeNeedsOffsetting) {
+ //get the local time offset from the server
+ dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
+ $scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z");
+ }
+ else {
+ //create a normal moment , no offset required
+ var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment();
+ }
+
+ element.datetimepicker("setValue", dateVal);
+ $scope.datetimePickerValue = dateVal.format($scope.model.config.format);
+ }
+
+ element.find("input").bind("blur", function() {
+ //we need to force an apply here
+ $scope.$apply();
+ });
+
+ //Ensure to remove the event handler when this instance is destroyted
+ $scope.$on('$destroy', function () {
+ element.find("input").unbind("blur");
+ element.datetimepicker("destroy");
+ });
+
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ setModelValue();
+ });
+ //unbind doc click event!
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+
+ });
+ });
+
+ });
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ setModelValue();
+ });
+
+ //unbind doc click event!
+ $scope.$on('$destroy', function () {
+ $(document).unbind("click", $scope.hidePicker);
+ unsubscribe();
+ });
+}
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.DatepickerController", dateTimePickerController);
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownController",
+ function($scope) {
+
+ //setup the default config
+ var config = {
+ items: [],
+ multiple: false
+ };
+
+ //map the user config
+ angular.extend(config, $scope.model.config);
+
+ //map back to the model
+ $scope.model.config = config;
+
+ function convertArrayToDictionaryArray(model){
+ //now we need to format the items in the dictionary because we always want to have an array
+ var newItems = [];
+ for (var i = 0; i < model.length; i++) {
+ newItems.push({ id: model[i], sortOrder: 0, value: model[i] });
+ }
+
+ return newItems;
+ }
+
+
+ function convertObjectToDictionaryArray(model){
+ //now we need to format the items in the dictionary because we always want to have an array
+ var newItems = [];
+ var vals = _.values($scope.model.config.items);
+ var keys = _.keys($scope.model.config.items);
+
+ for (var i = 0; i < vals.length; i++) {
+ var label = vals[i].value ? vals[i].value : vals[i];
+ newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label });
+ }
+
+ return newItems;
+ }
+
+ if (angular.isArray($scope.model.config.items)) {
+ //PP: I dont think this will happen, but we have tests that expect it to happen..
+ //if array is simple values, convert to array of objects
+ if(!angular.isObject($scope.model.config.items[0])){
+ $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
+ }
+ }
+ else if (angular.isObject($scope.model.config.items)) {
+ $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
+ }
+ else {
+ throw "The items property must be either an array or a dictionary";
+ }
+
+
+ //sort the values
+ $scope.model.config.items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
+
+ //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
+ // to "" gets selected by default
+ if ($scope.model.value === null || $scope.model.value === undefined) {
+ if ($scope.model.config.multiple) {
+ $scope.model.value = [];
+ }
+ else {
+ $scope.model.value = "";
+ }
+ }
+
+ });
+
+/** A drop down list or multi value select list based on an entity type, this can be re-used for any entity types */
+function entityPicker($scope, entityResource) {
+
+ //set the default to DocumentType
+ if (!$scope.model.config.entityType) {
+ $scope.model.config.entityType = "DocumentType";
+ }
+
+ //Determine the select list options and which value to publish
+ if (!$scope.model.config.publishBy) {
+ $scope.selectOptions = "entity.id as entity.name for entity in entities";
+ }
+ else {
+ $scope.selectOptions = "entity." + $scope.model.config.publishBy + " as entity.name for entity in entities";
+ }
+
+ entityResource.getAll($scope.model.config.entityType).then(function (data) {
+ //convert the ids to strings so the drop downs work properly when comparing
+ _.each(data, function(d) {
+ d.id = d.id.toString();
+ });
+ $scope.entities = data;
+ });
+
+ if ($scope.model.value === null || $scope.model.value === undefined) {
+ if ($scope.model.config.multiple) {
+ $scope.model.value = [];
+ }
+ else {
+ $scope.model.value = "";
+ }
+ }
+ else {
+ //if it's multiple, change the value to an array
+ if ($scope.model.config.multiple === "1") {
+ if (_.isString($scope.model.value)) {
+ $scope.model.value = $scope.model.value.split(',');
+ }
+ }
+ }
+}
+angular.module('umbraco').controller("Umbraco.PropertyEditors.EntityPickerController", entityPicker);
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.FileUploadController
+ * @function
+ *
+ * @description
+ * The controller for the file upload property editor. It is important to note that the $scope.model.value
+ * doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we
+ * are submitting files because in that case, we are adding files to the fileManager which is what gets peristed
+ * on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}"
+ * to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to
+ * be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows
+ * for the editors to check if the value has changed and to re-bind the property if that is true.
+ *
+*/
+function fileUploadController($scope, $element, $compile, imageHelper, fileManager, umbRequestHelper, mediaHelper) {
+
+ /** Clears the file collections when content is saving (if we need to clear) or after saved */
+ function clearFiles() {
+ //clear the files collection (we don't want to upload any!)
+ fileManager.setFiles($scope.model.alias, []);
+ //clear the current files
+ $scope.files = [];
+
+ if($scope.propertyForm) {
+ if ($scope.propertyForm.fileCount) {
+ //this is required to re-validate
+ $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
+ }
+ }
+
+ }
+
+ /** this method is used to initialize the data and to re-initialize it if the server value is changed */
+ function initialize(index) {
+
+ clearFiles();
+
+ if (!index) {
+ index = 1;
+ }
+
+ //this is used in order to tell the umb-single-file-upload directive to
+ //rebuild the html input control (and thus clearing the selected file) since
+ //that is the only way to manipulate the html for the file input control.
+ $scope.rebuildInput = {
+ index: index
+ };
+ //clear the current files
+ $scope.files = [];
+ //store the original value so we can restore it if the user clears and then cancels clearing.
+ $scope.originalValue = $scope.model.value;
+
+ //create the property to show the list of files currently saved
+ if ($scope.model.value != "" && $scope.model.value != undefined) {
+
+ var images = $scope.model.value.split(",");
+
+ $scope.persistedFiles = _.map(images, function (item) {
+ return { file: item, isImage: imageHelper.detectIfImageByExtension(item) };
+ });
+ }
+ else {
+ $scope.persistedFiles = [];
+ }
+
+ _.each($scope.persistedFiles, function (file) {
+
+ var thumbnailUrl = umbRequestHelper.getApiUrl(
+ "imagesApiBaseUrl",
+ "GetBigThumbnail",
+ [{ originalImagePath: file.file }]);
+
+ var extension = file.file.substring(file.file.lastIndexOf(".") + 1, file.file.length);
+
+ file.thumbnail = thumbnailUrl;
+ file.extension = extension.toLowerCase();
+ });
+
+ $scope.clearFiles = false;
+ }
+
+ initialize();
+
+ // Method required by the valPropertyValidator directive (returns true if the property editor has at least one file selected)
+ $scope.validateMandatory = function () {
+ return {
+ isValid: !$scope.model.validation.mandatory || ((($scope.persistedFiles != null && $scope.persistedFiles.length > 0) || ($scope.files != null && $scope.files.length > 0)) && !$scope.clearFiles),
+ errorMsg: "Value cannot be empty",
+ errorKey: "required"
+ };
+ }
+
+ //listen for clear files changes to set our model to be sent up to the server
+ $scope.$watch("clearFiles", function (isCleared) {
+ if (isCleared == true) {
+ $scope.model.value = { clearFiles: true };
+ clearFiles();
+ }
+ else {
+ //reset to original value
+ $scope.model.value = $scope.originalValue;
+ //this is required to re-validate
+ if($scope.propertyForm) {
+ $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
+ }
+ }
+ });
+
+ //listen for when a file is selected
+ $scope.$on("filesSelected", function (event, args) {
+ $scope.$apply(function () {
+ //set the files collection
+ fileManager.setFiles($scope.model.alias, args.files);
+ //clear the current files
+ $scope.files = [];
+ var newVal = "";
+ for (var i = 0; i < args.files.length; i++) {
+ //save the file object to the scope's files collection
+ $scope.files.push({ alias: $scope.model.alias, file: args.files[i] });
+ newVal += args.files[i].name + ",";
+ }
+
+ //this is required to re-validate
+ $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
+
+ //set clear files to false, this will reset the model too
+ $scope.clearFiles = false;
+ //set the model value to be the concatenation of files selected. Please see the notes
+ // in the description of this controller, it states that this value isn't actually used for persistence,
+ // but we need to set it so that the editor and the server can detect that it's been changed, and it is used for validation.
+ $scope.model.value = { selectedFiles: newVal.trimEnd(",") };
+ });
+ });
+
+ //listen for when the model value has changed
+ $scope.$watch("model.value", function (newVal, oldVal) {
+ //cannot just check for !newVal because it might be an empty string which we
+ //want to look for.
+ if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
+ //now we need to check if we need to re-initialize our structure which is kind of tricky
+ // since we only want to do that if the server has changed the value, not if this controller
+ // has changed the value. There's only 2 scenarios where we change the value internall so
+ // we know what those values can be, if they are not either of them, then we'll re-initialize.
+
+ if (newVal.clearFiles !== true && newVal !== $scope.originalValue && !newVal.selectedFiles) {
+ initialize($scope.rebuildInput.index + 1);
+ }
+
+ }
+ });
+};
+angular.module("umbraco")
+ .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController)
+ .run(function(mediaHelper, umbRequestHelper, assetsService){
+ if (mediaHelper && mediaHelper.registerFileResolver) {
+ assetsService.load(["lib/moment/moment-with-locales.js"]).then(
+ function () {
+ //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
+ // they contain different data structures so if we need to query against it we need to be aware of this.
+ mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){
+ if (thumbnail) {
+ if (mediaHelper.detectIfImageByExtension(property.value)) {
+ //get default big thumbnail from image processor
+ var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first";
+ return thumbnailUrl;
+ }
+ else {
+ return null;
+ }
+ }
+ else {
+ return property.value;
+ }
+ });
+ }
+ );
+ }
+ });
+
+angular.module("umbraco")
+
+//this controller is obsolete and should not be used anymore
+//it proxies everything to the system media list view which has overtaken
+//all the work this property editor used to perform
+.controller("Umbraco.PropertyEditors.FolderBrowserController",
+ function ($rootScope, $scope, contentTypeResource) {
+ //get the system media listview
+ contentTypeResource.getPropertyTypeScaffold(-96)
+ .then(function(dt) {
+
+ $scope.fakeProperty = {
+ alias: "contents",
+ config: dt.config,
+ description: "",
+ editor: dt.editor,
+ hideLabel: true,
+ id: 1,
+ label: "Contents:",
+ validation: {
+ mandatory: false,
+ pattern: null
+ },
+ value: "",
+ view: dt.view
+ };
+
+ });
+});
+
+angular.module("umbraco")
+.controller("Umbraco.PropertyEditors.GoogleMapsController",
+ function ($element, $rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) {
+
+ assetsService.loadJs('http://www.google.com/jsapi')
+ .then(function () {
+ google.load("maps", "3",
+ {
+ callback: initMap,
+ other_params: "sensor=false"
+ });
+ });
+
+ function initMap() {
+ //Google maps is available and all components are ready to use.
+ var valueArray = $scope.model.value.split(',');
+ var latLng = new google.maps.LatLng(valueArray[0], valueArray[1]);
+ var mapDiv = document.getElementById($scope.model.alias + '_map');
+ var mapOptions = {
+ zoom: $scope.model.config.zoom,
+ center: latLng,
+ mapTypeId: google.maps.MapTypeId[$scope.model.config.mapType]
+ };
+ var geocoder = new google.maps.Geocoder();
+ var map = new google.maps.Map(mapDiv, mapOptions);
+
+ var marker = new google.maps.Marker({
+ map: map,
+ position: latLng,
+ draggable: true
+ });
+
+ google.maps.event.addListener(map, 'click', function (event) {
+
+ dialogService.mediaPicker({
+ callback: function (data) {
+ var image = data.selection[0].src;
+
+ var latLng = event.latLng;
+ var marker = new google.maps.Marker({
+ map: map,
+ icon: image,
+ position: latLng,
+ draggable: true
+ });
+
+ google.maps.event.addListener(marker, "dragend", function (e) {
+ var newLat = marker.getPosition().lat();
+ var newLng = marker.getPosition().lng();
+
+ codeLatLng(marker.getPosition(), geocoder);
+
+ //set the model value
+ $scope.model.vvalue = newLat + "," + newLng;
+ });
+
+ }
+ });
+ });
+
+ var tabShown = function(e) {
+ google.maps.event.trigger(map, 'resize');
+ };
+
+ //listen for tab changes
+ if (tabsCtrl != null) {
+ tabsCtrl.onTabShown(function (args) {
+ tabShown();
+ });
+ }
+
+ $element.closest('.umb-panel.tabbable').on('shown', '.nav-tabs a', tabShown);
+
+ $scope.$on('$destroy', function () {
+ $element.closest('.umb-panel.tabbable').off('shown', '.nav-tabs a', tabShown);
+ });
+ }
+
+ function codeLatLng(latLng, geocoder) {
+ geocoder.geocode({ 'latLng': latLng },
+ function (results, status) {
+ if (status == google.maps.GeocoderStatus.OK) {
+ var location = results[0].formatted_address;
+ $rootScope.$apply(function () {
+ notificationsService.success("Peter just went to: ", location);
+ });
+ }
+ });
+ }
+
+ //here we declare a special method which will be called whenever the value has changed from the server
+ //this is instead of doing a watch on the model.value = faster
+ $scope.model.onValueChanged = function (newVal, oldVal) {
+ //update the display val again if it has changed from the server
+ initMap();
+ };
+ });
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController",
+ function ($scope) {
+
+ $scope.currentLayout = $scope.model.currentLayout;
+ $scope.columns = $scope.model.columns;
+ $scope.rows = $scope.model.rows;
+
+ $scope.scaleUp = function(section, max, overflow){
+ var add = 1;
+ if(overflow !== true){
+ add = (max > 1) ? 1 : max;
+ }
+ //var add = (max > 1) ? 1 : max;
+ section.grid = section.grid+add;
+ };
+
+ $scope.scaleDown = function(section){
+ var remove = (section.grid > 1) ? 1 : 0;
+ section.grid = section.grid-remove;
+ };
+
+ $scope.percentage = function(spans){
+ return ((spans / $scope.columns) * 100).toFixed(8);
+ };
+
+ $scope.toggleCollection = function(collection, toggle){
+ if(toggle){
+ collection = [];
+ }else{
+ delete collection;
+ }
+ };
+
+
+
+ /****************
+ Section
+ *****************/
+ $scope.configureSection = function(section, template){
+ if(section === undefined){
+ var space = ($scope.availableLayoutSpace > 4) ? 4 : $scope.availableLayoutSpace;
+ section = {
+ grid: space
+ };
+ template.sections.push(section);
+ }
+
+ $scope.currentSection = section;
+ };
+
+ $scope.deleteSection = function(section, template) {
+ if ($scope.currentSection === section) {
+ $scope.currentSection = undefined;
+ }
+ var index = template.sections.indexOf(section)
+ template.sections.splice(index, 1);
+ };
+
+ $scope.closeSection = function(){
+ $scope.currentSection = undefined;
+ };
+
+ $scope.$watch("currentLayout", function(layout){
+ if(layout){
+ var total = 0;
+ _.forEach(layout.sections, function(section){
+ total = (total + section.grid);
+ });
+
+ $scope.availableLayoutSpace = $scope.columns - total;
+ }
+ }, true);
+ });
+
+function RowConfigController($scope) {
+
+ $scope.currentRow = $scope.model.currentRow;
+ $scope.editors = $scope.model.editors;
+ $scope.columns = $scope.model.columns;
+
+ $scope.scaleUp = function(section, max, overflow) {
+ var add = 1;
+ if (overflow !== true) {
+ add = (max > 1) ? 1 : max;
+ }
+ //var add = (max > 1) ? 1 : max;
+ section.grid = section.grid + add;
+ };
+
+ $scope.scaleDown = function(section) {
+ var remove = (section.grid > 1) ? 1 : 0;
+ section.grid = section.grid - remove;
+ };
+
+ $scope.percentage = function(spans) {
+ return ((spans / $scope.columns) * 100).toFixed(8);
+ };
+
+ $scope.toggleCollection = function(collection, toggle) {
+ if (toggle) {
+ collection = [];
+ }
+ else {
+ delete collection;
+ }
+ };
+
+
+ /****************
+ area
+ *****************/
+ $scope.configureCell = function(cell, row) {
+ if ($scope.currentCell && $scope.currentCell === cell) {
+ delete $scope.currentCell;
+ }
+ else {
+ if (cell === undefined) {
+ var available = $scope.availableRowSpace;
+ var space = 4;
+
+ if (available < 4 && available > 0) {
+ space = available;
+ }
+
+ cell = {
+ grid: space
+ };
+
+ row.areas.push(cell);
+ }
+ $scope.currentCell = cell;
+ }
+ };
+
+ $scope.deleteArea = function (cell, row) {
+ if ($scope.currentCell === cell) {
+ $scope.currentCell = undefined;
+ }
+ var index = row.areas.indexOf(cell)
+ row.areas.splice(index, 1);
+ };
+
+ $scope.closeArea = function() {
+ $scope.currentCell = undefined;
+ };
+
+ $scope.nameChanged = false;
+ var originalName = $scope.currentRow.name;
+ $scope.$watch("currentRow", function(row) {
+ if (row) {
+
+ var total = 0;
+ _.forEach(row.areas, function(area) {
+ total = (total + area.grid);
+ });
+
+ $scope.availableRowSpace = $scope.columns - total;
+
+ if (originalName) {
+ if (originalName != row.name) {
+ $scope.nameChanged = true;
+ }
+ else {
+ $scope.nameChanged = false;
+ }
+ }
+ }
+ }, true);
+
+}
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController", RowConfigController);
+
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.Grid.EmbedController",
+ function ($scope, $rootScope, $timeout) {
+
+ $scope.setEmbed = function(){
+ $scope.embedDialog = {};
+ $scope.embedDialog.view = "embed";
+ $scope.embedDialog.show = true;
+
+ $scope.embedDialog.submit = function(model) {
+ $scope.control.value = model.embed.preview;
+ $scope.embedDialog.show = false;
+ $scope.embedDialog = null;
+ };
+
+ $scope.embedDialog.close = function(oldModel) {
+ $scope.embedDialog.show = false;
+ $scope.embedDialog = null;
+ };
+
+ };
+
+ $timeout(function(){
+ if($scope.control.$initializing){
+ $scope.setEmbed();
+ }
+ }, 200);
+});
+
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.Grid.MacroController",
+ function ($scope, $rootScope, $timeout, dialogService, macroResource, macroService, $routeParams) {
+
+ $scope.title = "Click to insert macro";
+
+ $scope.setMacro = function(){
+
+ var dialogData = {
+ richTextEditor: true,
+ macroData: $scope.control.value || {
+ macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias
+ ? $scope.control.editor.config.macroAlias : ""
+ }
+ };
+
+ $scope.macroPickerOverlay = {};
+ $scope.macroPickerOverlay.view = "macropicker";
+ $scope.macroPickerOverlay.dialogData = dialogData;
+ $scope.macroPickerOverlay.show = true;
+
+ $scope.macroPickerOverlay.submit = function(model) {
+
+ var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
+
+ $scope.control.value = {
+ macroAlias: macroObject.macroAlias,
+ macroParamsDictionary: macroObject.macroParamsDictionary
+ };
+
+ $scope.setPreview($scope.control.value );
+
+ $scope.macroPickerOverlay.show = false;
+ $scope.macroPickerOverlay = null;
+ };
+
+ $scope.macroPickerOverlay.close = function(oldModel) {
+ $scope.macroPickerOverlay.show = false;
+ $scope.macroPickerOverlay = null;
+ };
+
+ };
+
+ $scope.setPreview = function(macro){
+ var contentId = $routeParams.id;
+
+ macroResource.getMacroResultAsHtmlForEditor(macro.macroAlias, contentId, macro.macroParamsDictionary)
+ .then(function (htmlResult) {
+ $scope.title = macro.macroAlias;
+ if(htmlResult.trim().length > 0 && htmlResult.indexOf("Macro:") < 0){
+ $scope.preview = htmlResult;
+ }
+ });
+
+ };
+
+ $timeout(function(){
+ if($scope.control.$initializing){
+ $scope.setMacro();
+ }else if($scope.control.value){
+ $scope.setPreview($scope.control.value);
+ }
+ }, 200);
+});
+
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.Grid.MediaController",
+ function ($scope, $rootScope, $timeout) {
+
+ $scope.setImage = function(){
+ $scope.mediaPickerOverlay = {};
+ $scope.mediaPickerOverlay.view = "mediapicker";
+ $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined;
+ $scope.mediaPickerOverlay.showDetails = true;
+ $scope.mediaPickerOverlay.disableFolderSelect = true;
+ $scope.mediaPickerOverlay.onlyImages = true;
+ $scope.mediaPickerOverlay.show = true;
+
+ $scope.mediaPickerOverlay.submit = function(model) {
+ var selectedImage = model.selectedImages[0];
+
+ $scope.control.value = {
+ focalPoint: selectedImage.focalPoint,
+ id: selectedImage.id,
+ image: selectedImage.image,
+ altText: selectedImage.altText
+ };
+
+ $scope.setUrl();
+
+ $scope.mediaPickerOverlay.show = false;
+ $scope.mediaPickerOverlay = null;
+ };
+
+ $scope.mediaPickerOverlay.close = function(oldModel) {
+ $scope.mediaPickerOverlay.show = false;
+ $scope.mediaPickerOverlay = null;
+ };
+ };
+
+ $scope.setUrl = function(){
+
+ if($scope.control.value.image){
+ var url = $scope.control.value.image;
+
+ if($scope.control.editor.config && $scope.control.editor.config.size){
+ url += "?width=" + $scope.control.editor.config.size.width;
+ url += "&height=" + $scope.control.editor.config.size.height;
+ url += "&animationprocessmode=first";
+
+ if($scope.control.value.focalPoint){
+ url += "¢er=" + $scope.control.value.focalPoint.top +"," + $scope.control.value.focalPoint.left;
+ url += "&mode=crop";
+ }
+ }
+
+ // set default size if no crop present (moved from the view)
+ if (url.indexOf('?') == -1)
+ {
+ url += "?width=800&upscale=false&animationprocessmode=false"
+ }
+ $scope.url = url;
+ }
+ };
+
+ $timeout(function(){
+ if($scope.control.$initializing){
+ $scope.setImage();
+ }else if($scope.control.value){
+ $scope.setUrl();
+ }
+ }, 200);
+});
+
+(function() {
+ "use strict";
+
+ function GridRichTextEditorController($scope, tinyMceService, macroService) {
+
+ var vm = this;
+
+ vm.openLinkPicker = openLinkPicker;
+ vm.openMediaPicker = openMediaPicker;
+ vm.openMacroPicker = openMacroPicker;
+ vm.openEmbed = openEmbed;
+
+ function openLinkPicker(editor, currentTarget, anchorElement) {
+ vm.linkPickerOverlay = {
+ view: "linkpicker",
+ currentTarget: currentTarget,
+ show: true,
+ submit: function(model) {
+ tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
+ vm.linkPickerOverlay.show = false;
+ vm.linkPickerOverlay = null;
+ }
+ };
+ }
+
+ function openMediaPicker(editor, currentTarget, userData) {
+ vm.mediaPickerOverlay = {
+ currentTarget: currentTarget,
+ onlyImages: true,
+ showDetails: true,
+ startNodeId: userData.startMediaId,
+ view: "mediapicker",
+ show: true,
+ submit: function(model) {
+ tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]);
+ vm.mediaPickerOverlay.show = false;
+ vm.mediaPickerOverlay = null;
+ }
+ };
+ }
+
+ function openEmbed(editor) {
+ vm.embedOverlay = {
+ view: "embed",
+ show: true,
+ submit: function(model) {
+ tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview);
+ vm.embedOverlay.show = false;
+ vm.embedOverlay = null;
+ }
+ };
+ }
+
+ function openMacroPicker(editor, dialogData) {
+ vm.macroPickerOverlay = {
+ view: "macropicker",
+ dialogData: dialogData,
+ show: true,
+ submit: function(model) {
+ var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
+ tinyMceService.insertMacroInEditor(editor, macroObject, $scope);
+ vm.macroPickerOverlay.show = false;
+ vm.macroPickerOverlay = null;
+ }
+ };
+ }
+
+
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.PropertyEditors.Grid.RichTextEditorController", GridRichTextEditorController);
+
+})();
+
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.Grid.TextStringController",
+ function ($scope, $rootScope, $timeout, dialogService) {
+
+
+
+ });
+
+
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.GridController",
+ function ($scope, $http, assetsService, localizationService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper, angularHelper) {
+
+ // Grid status variables
+ var placeHolder = "";
+ var currentForm = angularHelper.getCurrentForm($scope);
+
+ $scope.currentRow = null;
+ $scope.currentCell = null;
+ $scope.currentToolsControl = null;
+ $scope.currentControl = null;
+ $scope.openRTEToolbarId = null;
+ $scope.hasSettings = false;
+ $scope.showRowConfigurations = true;
+ $scope.sortMode = false;
+ $scope.reorderKey = "general_reorder";
+
+ // *********************************************
+ // Sortable options
+ // *********************************************
+
+ var draggedRteSettings;
+
+ $scope.sortableOptionsRow = {
+ distance: 10,
+ cursor: "move",
+ placeholder: "ui-sortable-placeholder",
+ handle: ".umb-row-title-bar",
+ helper: "clone",
+ forcePlaceholderSize: true,
+ tolerance: "pointer",
+ zIndex: 999999999999999999,
+ scrollSensitivity: 100,
+ cursorAt: {
+ top: 40,
+ left: 60
+ },
+
+ sort: function (event, ui) {
+ /* prevent vertical scroll out of the screen */
+ var max = $(".umb-grid").width() - 150;
+ if (parseInt(ui.helper.css("left")) > max) {
+ ui.helper.css({ "left": max + "px" });
+ }
+ if (parseInt(ui.helper.css("left")) < 20) {
+ ui.helper.css({ "left": 20 });
+ }
+ },
+
+ start: function (e, ui) {
+
+ // Fade out row when sorting
+ ui.item.context.style.display = "block";
+ ui.item.context.style.opacity = "0.5";
+
+ draggedRteSettings = {};
+ ui.item.find(".mceNoEditor").each(function () {
+ // remove all RTEs in the dragged row and save their settings
+ var id = $(this).attr("id");
+ draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings;
+ // tinyMCE.execCommand("mceRemoveEditor", false, id);
+ });
+ },
+
+ stop: function (e, ui) {
+
+ // Fade in row when sorting stops
+ ui.item.context.style.opacity = "1";
+
+ // reset all RTEs affected by the dragging
+ ui.item.parents(".umb-column").find(".mceNoEditor").each(function () {
+ var id = $(this).attr("id");
+ draggedRteSettings[id] = draggedRteSettings[id] || _.findWhere(tinyMCE.editors, { id: id }).settings;
+ tinyMCE.execCommand("mceRemoveEditor", false, id);
+ tinyMCE.init(draggedRteSettings[id]);
+ });
+ currentForm.$setDirty();
+ }
+ };
+
+ var notIncludedRte = [];
+ var cancelMove = false;
+
+ $scope.sortableOptionsCell = {
+ distance: 10,
+ cursor: "move",
+ placeholder: "ui-sortable-placeholder",
+ handle: ".umb-control-handle",
+ helper: "clone",
+ connectWith: ".umb-cell-inner",
+ forcePlaceholderSize: true,
+ tolerance: "pointer",
+ zIndex: 999999999999999999,
+ scrollSensitivity: 100,
+ cursorAt: {
+ top: 45,
+ left: 90
+ },
+
+ sort: function (event, ui) {
+
+ /* prevent vertical scroll out of the screen */
+ var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css("left")) - parseInt($(".umb-grid").offset().left);
+ var max = $(".umb-grid").width() - 220;
+ if (position > max) {
+ ui.helper.css({ "left": max - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" });
+ }
+ if (position < 0) {
+ ui.helper.css({ "left": 0 - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" });
+ }
+ },
+
+ over: function (event, ui) {
+ var allowedEditors = $(event.target).scope().area.allowed;
+
+ if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors) {
+
+ $scope.$apply(function () {
+ $(event.target).scope().area.dropNotAllowed = true;
+ });
+
+ ui.placeholder.hide();
+ cancelMove = true;
+ }
+ else {
+ if ($(event.target).scope().area.controls.length == 0){
+
+ $scope.$apply(function () {
+ $(event.target).scope().area.dropOnEmpty = true;
+ });
+ ui.placeholder.hide();
+ } else {
+ ui.placeholder.show();
+ }
+ cancelMove = false;
+ }
+ },
+
+ out: function(event, ui) {
+ $scope.$apply(function () {
+ $(event.target).scope().area.dropNotAllowed = false;
+ $(event.target).scope().area.dropOnEmpty = false;
+ });
+ },
+
+ update: function (event, ui) {
+ /* add all RTEs which are affected by the dragging */
+ if (!ui.sender) {
+ if (cancelMove) {
+ ui.item.sortable.cancel();
+ }
+ ui.item.parents(".umb-cell.content").find(".mceNoEditor").each(function () {
+ if ($.inArray($(this).attr("id"), notIncludedRte) < 0) {
+ notIncludedRte.splice(0, 0, $(this).attr("id"));
+ }
+ });
+ }
+ else {
+ $(event.target).find(".mceNoEditor").each(function () {
+ if ($.inArray($(this).attr("id"), notIncludedRte) < 0) {
+ notIncludedRte.splice(0, 0, $(this).attr("id"));
+ }
+ });
+ }
+ currentForm.$setDirty();
+ },
+
+ start: function (e, ui) {
+
+ // fade out control when sorting
+ ui.item.context.style.display = "block";
+ ui.item.context.style.opacity = "0.5";
+
+ // reset dragged RTE settings in case a RTE isn't dragged
+ draggedRteSettings = undefined;
+ ui.item.context.style.display = "block";
+ ui.item.find(".mceNoEditor").each(function () {
+ notIncludedRte = [];
+ var editors = _.findWhere(tinyMCE.editors, { id: $(this).attr("id") });
+
+ // save the dragged RTE settings
+ if(editors) {
+ draggedRteSettings = editors.settings;
+
+ // remove the dragged RTE
+ tinyMCE.execCommand("mceRemoveEditor", false, $(this).attr("id"));
+
+ }
+
+ });
+ },
+
+ stop: function (e, ui) {
+
+ // Fade in control when sorting stops
+ ui.item.context.style.opacity = "1";
+
+ ui.item.parents(".umb-cell-content").find(".mceNoEditor").each(function () {
+ if ($.inArray($(this).attr("id"), notIncludedRte) < 0) {
+ // add all dragged's neighbouring RTEs in the new cell
+ notIncludedRte.splice(0, 0, $(this).attr("id"));
+ }
+ });
+ $timeout(function () {
+ // reconstruct the dragged RTE (could be undefined when dragging something else than RTE)
+ if (draggedRteSettings !== undefined) {
+ tinyMCE.init(draggedRteSettings);
+ }
+
+ _.forEach(notIncludedRte, function (id) {
+ // reset all the other RTEs
+ if (draggedRteSettings === undefined || id !== draggedRteSettings.id) {
+ var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings;
+ tinyMCE.execCommand("mceRemoveEditor", false, id);
+ tinyMCE.init(rteSettings);
+ }
+ });
+ }, 500, false);
+
+ $scope.$apply(function () {
+
+ var cell = $(e.target).scope().area;
+ cell.hasActiveChild = hasActiveChild(cell, cell.controls);
+ cell.active = false;
+ });
+ }
+
+ };
+
+ $scope.toggleSortMode = function() {
+ $scope.sortMode = !$scope.sortMode;
+ if($scope.sortMode) {
+ $scope.reorderKey = "general_reorderDone";
+ } else {
+ $scope.reorderKey = "general_reorder";
+ }
+ };
+
+ $scope.showReorderButton = function() {
+ if($scope.model.value && $scope.model.value.sections) {
+ for(var i = 0; $scope.model.value.sections.length > i; i++) {
+ var section = $scope.model.value.sections[i];
+ if(section.rows && section.rows.length > 0) {
+ return true;
+ }
+ }
+ }
+ };
+
+ // *********************************************
+ // Add items overlay menu
+ // *********************************************
+ $scope.openEditorOverlay = function(event, area, index, key) {
+ $scope.editorOverlay = {
+ view: "itempicker",
+ filter: false,
+ title: localizationService.localize("grid_insertControl"),
+ availableItems: area.$allowedEditors,
+ event: event,
+ show: true,
+ submit: function(model) {
+ $scope.addControl(model.selectedItem, area, index);
+ $scope.editorOverlay.show = false;
+ $scope.editorOverlay = null;
+ }
+ };
+ };
+
+ // *********************************************
+ // Template management functions
+ // *********************************************
+
+ $scope.addTemplate = function (template) {
+ $scope.model.value = angular.copy(template);
+
+ //default row data
+ _.forEach($scope.model.value.sections, function (section) {
+ $scope.initSection(section);
+ });
+ };
+
+
+ // *********************************************
+ // Row management function
+ // *********************************************
+
+ $scope.clickRow = function(index, rows) {
+ rows[index].active = true;
+ };
+
+ $scope.clickOutsideRow = function(index, rows) {
+ rows[index].active = false;
+ };
+
+ function getAllowedLayouts(section) {
+
+ var layouts = $scope.model.config.items.layouts;
+
+ //This will occur if it is a new section which has been
+ // created from a 'template'
+ if (section.allowed && section.allowed.length > 0) {
+ return _.filter(layouts, function (layout) {
+ return _.indexOf(section.allowed, layout.name) >= 0;
+ });
+ }
+ else {
+
+
+ return layouts;
+ }
+ }
+
+ $scope.addRow = function (section, layout) {
+
+ //copy the selected layout into the rows collection
+ var row = angular.copy(layout);
+
+ // Init row value
+ row = $scope.initRow(row);
+
+ // Push the new row
+ if (row) {
+ section.rows.push(row);
+ }
+
+ currentForm.$setDirty();
+
+ $scope.showRowConfigurations = false;
+
+ };
+
+ $scope.removeRow = function (section, $index) {
+ if (section.rows.length > 0) {
+ section.rows.splice($index, 1);
+ $scope.currentRow = null;
+ $scope.openRTEToolbarId = null;
+ currentForm.$setDirty();
+ }
+
+ if(section.rows.length === 0) {
+ $scope.showRowConfigurations = true;
+ }
+ };
+
+ var shouldApply = function(item, itemType, gridItem) {
+ if (item.applyTo === undefined || item.applyTo === null || item.applyTo === "") {
+ return true;
+ }
+
+ if (typeof (item.applyTo) === "string") {
+ return item.applyTo === itemType;
+ }
+
+ if (itemType === "row") {
+ if (item.applyTo.row === undefined) {
+ return false;
+ }
+ if (item.applyTo.row === null || item.applyTo.row === "") {
+ return true;
+ }
+ var rows = item.applyTo.row.split(',');
+ return _.indexOf(rows, gridItem.name) !== -1;
+ } else if (itemType === "cell") {
+ if (item.applyTo.cell === undefined) {
+ return false;
+ }
+ if (item.applyTo.cell === null || item.applyTo.cell === "") {
+ return true;
+ }
+ var cells = item.applyTo.cell.split(',');
+ var cellSize = gridItem.grid.toString();
+ return _.indexOf(cells, cellSize) !== -1;
+ }
+ }
+
+ $scope.editGridItemSettings = function (gridItem, itemType) {
+
+ placeHolder = "{0}";
+
+ var styles, config;
+ if (itemType === 'control') {
+ styles = null;
+ config = angular.copy(gridItem.editor.config.settings);
+ } else {
+ styles = _.filter(angular.copy($scope.model.config.items.styles), function (item) { return shouldApply(item, itemType, gridItem); });
+ config = _.filter(angular.copy($scope.model.config.items.config), function (item) { return shouldApply(item, itemType, gridItem); });
+ }
+
+ if(angular.isObject(gridItem.config)){
+ _.each(config, function(cfg){
+ var val = gridItem.config[cfg.key];
+ if(val){
+ cfg.value = stripModifier(val, cfg.modifier);
+ }
+ });
+ }
+
+ if(angular.isObject(gridItem.styles)){
+ _.each(styles, function(style){
+ var val = gridItem.styles[style.key];
+ if(val){
+ style.value = stripModifier(val, style.modifier);
+ }
+ });
+ }
+
+ $scope.gridItemSettingsDialog = {};
+ $scope.gridItemSettingsDialog.view = "views/propertyeditors/grid/dialogs/config.html";
+ $scope.gridItemSettingsDialog.title = "Settings";
+ $scope.gridItemSettingsDialog.styles = styles;
+ $scope.gridItemSettingsDialog.config = config;
+
+ $scope.gridItemSettingsDialog.show = true;
+
+ $scope.gridItemSettingsDialog.submit = function(model) {
+
+ var styleObject = {};
+ var configObject = {};
+
+ _.each(model.styles, function(style){
+ if(style.value){
+ styleObject[style.key] = addModifier(style.value, style.modifier);
+ }
+ });
+ _.each(model.config, function (cfg) {
+ if (cfg.value) {
+ configObject[cfg.key] = addModifier(cfg.value, cfg.modifier);
+ }
+ });
+
+ gridItem.styles = styleObject;
+ gridItem.config = configObject;
+ gridItem.hasConfig = gridItemHasConfig(styleObject, configObject);
+
+ currentForm.$setDirty();
+
+ $scope.gridItemSettingsDialog.show = false;
+ $scope.gridItemSettingsDialog = null;
+ };
+
+ $scope.gridItemSettingsDialog.close = function(oldModel) {
+ $scope.gridItemSettingsDialog.show = false;
+ $scope.gridItemSettingsDialog = null;
+ };
+
+ };
+
+ function stripModifier(val, modifier) {
+ if (!val || !modifier || modifier.indexOf(placeHolder) < 0) {
+ return val;
+ } else {
+ var paddArray = modifier.split(placeHolder);
+ if(paddArray.length == 1){
+ if (modifier.indexOf(placeHolder) === 0) {
+ return val.slice(0, -paddArray[0].length);
+ } else {
+ return val.slice(paddArray[0].length, 0);
+ }
+ } else {
+ if (paddArray[1].length === 0) {
+ return val.slice(paddArray[0].length);
+ }
+ return val.slice(paddArray[0].length, -paddArray[1].length);
+ }
+ }
+ }
+
+ var addModifier = function(val, modifier){
+ if (!modifier || modifier.indexOf(placeHolder) < 0) {
+ return val;
+ } else {
+ return modifier.replace(placeHolder, val);
+ }
+ };
+
+ function gridItemHasConfig(styles, config) {
+
+ if(_.isEmpty(styles) && _.isEmpty(config)) {
+ return false;
+ } else {
+ return true;
+ }
+
+ }
+
+ // *********************************************
+ // Area management functions
+ // *********************************************
+
+ $scope.clickCell = function(index, cells, row) {
+ cells[index].active = true;
+ row.hasActiveChild = true;
+ };
+
+ $scope.clickOutsideCell = function(index, cells, row) {
+ cells[index].active = false;
+ row.hasActiveChild = hasActiveChild(row, cells);
+ };
+
+ $scope.cellPreview = function (cell) {
+ if (cell && cell.$allowedEditors) {
+ var editor = cell.$allowedEditors[0];
+ return editor.icon;
+ } else {
+ return "icon-layout";
+ }
+ };
+
+
+ // *********************************************
+ // Control management functions
+ // *********************************************
+ $scope.clickControl = function (index, controls, cell) {
+ controls[index].active = true;
+ cell.hasActiveChild = true;
+ };
+
+ $scope.clickOutsideControl = function (index, controls, cell) {
+ controls[index].active = false;
+ cell.hasActiveChild = hasActiveChild(cell, controls);
+ };
+
+ function hasActiveChild(item, children) {
+
+ var activeChild = false;
+
+ for(var i = 0; children.length > i; i++) {
+ var child = children[i];
+
+ if(child.active) {
+ activeChild = true;
+ }
+ }
+
+ if(activeChild) {
+ return true;
+ }
+
+ }
+
+
+ var guid = (function () {
+ function s4() {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+ }
+ return function () {
+ return s4() + s4() + "-" + s4() + "-" + s4() + "-" +
+ s4() + "-" + s4() + s4() + s4();
+ };
+ })();
+
+ $scope.setUniqueId = function (cell, index) {
+ return guid();
+ };
+
+ $scope.addControl = function (editor, cell, index, initialize) {
+
+ initialize = (initialize !== false);
+
+ var newControl = {
+ value: null,
+ editor: editor,
+ $initializing: initialize
+ };
+
+ if (index === undefined) {
+ index = cell.controls.length;
+ }
+
+ newControl.active = true;
+
+ //populate control
+ $scope.initControl(newControl, index + 1);
+
+ cell.controls.push(newControl);
+
+ };
+
+ $scope.addTinyMce = function (cell) {
+ var rte = $scope.getEditor("rte");
+ $scope.addControl(rte, cell);
+ };
+
+ $scope.getEditor = function (alias) {
+ return _.find($scope.availableEditors, function (editor) { return editor.alias === alias; });
+ };
+
+ $scope.removeControl = function (cell, $index) {
+ $scope.currentControl = null;
+ cell.controls.splice($index, 1);
+ };
+
+ $scope.percentage = function (spans) {
+ return ((spans / $scope.model.config.items.columns) * 100).toFixed(8);
+ };
+
+
+ $scope.clearPrompt = function (scopedObject, e) {
+ scopedObject.deletePrompt = false;
+ e.preventDefault();
+ e.stopPropagation();
+ };
+
+ $scope.togglePrompt = function (scopedObject) {
+ scopedObject.deletePrompt = !scopedObject.deletePrompt;
+ };
+
+ $scope.hidePrompt = function (scopedObject) {
+ scopedObject.deletePrompt = false;
+ };
+
+ $scope.toggleAddRow = function() {
+ $scope.showRowConfigurations = !$scope.showRowConfigurations;
+ };
+
+
+ // *********************************************
+ // Initialization
+ // these methods are called from ng-init on the template
+ // so we can controll their first load data
+ //
+ // intialization sets non-saved data like percentage sizing, allowed editors and
+ // other data that should all be pre-fixed with $ to strip it out on save
+ // *********************************************
+
+ // *********************************************
+ // Init template + sections
+ // *********************************************
+ $scope.initContent = function () {
+ var clear = true;
+
+ //settings indicator shortcut
+ if ( ($scope.model.config.items.config && $scope.model.config.items.config.length > 0) || ($scope.model.config.items.styles && $scope.model.config.items.styles.length > 0)) {
+ $scope.hasSettings = true;
+ }
+
+ //ensure the grid has a column value set,
+ //if nothing is found, set it to 12
+ if ($scope.model.config.items.columns && angular.isString($scope.model.config.items.columns)) {
+ $scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
+ } else {
+ $scope.model.config.items.columns = 12;
+ }
+
+ if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) {
+
+ if ($scope.model.value.name && angular.isArray($scope.model.config.items.templates)) {
+
+ //This will occur if it is an existing value, in which case
+ // we need to determine which layout was applied by looking up
+ // the name
+ // TODO: We need to change this to an immutable ID!!
+
+ var found = _.find($scope.model.config.items.templates, function (t) {
+ return t.name === $scope.model.value.name;
+ });
+
+ if (found && angular.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) {
+
+ //Cool, we've found the template associated with our current value with matching sections counts, now we need to
+ // merge this template data on to our current value (as if it was new) so that we can preserve what is and isn't
+ // allowed for this template based on the current config.
+
+ _.each(found.sections, function (templateSection, index) {
+ angular.extend($scope.model.value.sections[index], angular.copy(templateSection));
+ });
+
+ }
+ }
+
+ _.forEach($scope.model.value.sections, function (section, index) {
+
+ if (section.grid > 0) {
+ $scope.initSection(section);
+
+ //we do this to ensure that the grid can be reset by deleting the last row
+ if (section.rows.length > 0) {
+ clear = false;
+ }
+ } else {
+ $scope.model.value.sections.splice(index, 1);
+ }
+ });
+ } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) {
+ $scope.addTemplate($scope.model.config.items.templates[0]);
+ clear = false;
+ }
+
+ if (clear) {
+ $scope.model.value = undefined;
+ }
+ };
+
+ $scope.initSection = function (section) {
+ section.$percentage = $scope.percentage(section.grid);
+
+ section.$allowedLayouts = getAllowedLayouts(section);
+
+ if (!section.rows || section.rows.length === 0) {
+ section.rows = [];
+ if(section.$allowedLayouts.length === 1){
+ $scope.addRow(section, section.$allowedLayouts[0]);
+ }
+ } else {
+ _.forEach(section.rows, function (row, index) {
+ if (!row.$initialized) {
+ var initd = $scope.initRow(row);
+
+ //if init fails, remove
+ if (!initd) {
+ section.rows.splice(index, 1);
+ } else {
+ section.rows[index] = initd;
+ }
+ }
+ });
+
+ // if there is more than one row added - hide row add tools
+ $scope.showRowConfigurations = false;
+ }
+ };
+
+
+ // *********************************************
+ // Init layout / row
+ // *********************************************
+ $scope.initRow = function (row) {
+
+ //merge the layout data with the original config data
+ //if there are no config info on this, splice it out
+ var original = _.find($scope.model.config.items.layouts, function (o) { return o.name === row.name; });
+
+ if (!original) {
+ return null;
+ } else {
+ //make a copy to not touch the original config
+ original = angular.copy(original);
+ original.styles = row.styles;
+ original.config = row.config;
+ original.hasConfig = gridItemHasConfig(row.styles, row.config);
+
+
+ //sync area configuration
+ _.each(original.areas, function (area, areaIndex) {
+
+
+ if (area.grid > 0) {
+ var currentArea = row.areas[areaIndex];
+
+ if (currentArea) {
+ area.config = currentArea.config;
+ area.styles = currentArea.styles;
+ area.hasConfig = gridItemHasConfig(currentArea.styles, currentArea.config);
+ }
+
+ //set editor permissions
+ if (!area.allowed || area.allowAll === true) {
+ area.$allowedEditors = $scope.availableEditors;
+ area.$allowsRTE = true;
+ } else {
+ area.$allowedEditors = _.filter($scope.availableEditors, function (editor) {
+ return _.indexOf(area.allowed, editor.alias) >= 0;
+ });
+
+ if (_.indexOf(area.allowed, "rte") >= 0) {
+ area.$allowsRTE = true;
+ }
+ }
+
+ //copy over existing controls into the new areas
+ if (row.areas.length > areaIndex && row.areas[areaIndex].controls) {
+ area.controls = currentArea.controls;
+
+ _.forEach(area.controls, function (control, controlIndex) {
+ $scope.initControl(control, controlIndex);
+ });
+
+ } else {
+ //if empty
+ area.controls = [];
+
+ //if only one allowed editor
+ if(area.$allowedEditors.length === 1){
+ $scope.addControl(area.$allowedEditors[0], area, 0, false);
+ }
+ }
+
+ //set width
+ area.$percentage = $scope.percentage(area.grid);
+ area.$uniqueId = $scope.setUniqueId();
+
+ } else {
+ original.areas.splice(areaIndex, 1);
+ }
+ });
+
+ //replace the old row
+ original.$initialized = true;
+
+ //set a disposable unique ID
+ original.$uniqueId = $scope.setUniqueId();
+
+ //set a no disposable unique ID (util for row styling)
+ original.id = !row.id ? $scope.setUniqueId() : row.id;
+
+ return original;
+ }
+
+ };
+
+
+ // *********************************************
+ // Init control
+ // *********************************************
+
+ $scope.initControl = function (control, index) {
+ control.$index = index;
+ control.$uniqueId = $scope.setUniqueId();
+
+ //error handling in case of missing editor..
+ //should only happen if stripped earlier
+ if (!control.editor) {
+ control.$editorPath = "views/propertyeditors/grid/editors/error.html";
+ }
+
+ if (!control.$editorPath) {
+ var editorConfig = $scope.getEditor(control.editor.alias);
+
+ if (editorConfig) {
+ control.editor = editorConfig;
+
+ //if its an absolute path
+ if (control.editor.view.startsWith("/") || control.editor.view.startsWith("~/")) {
+ control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view);
+ }
+ else {
+ //use convention
+ control.$editorPath = "views/propertyeditors/grid/editors/" + control.editor.view + ".html";
+ }
+ }
+ else {
+ control.$editorPath = "views/propertyeditors/grid/editors/error.html";
+ }
+ }
+
+
+ };
+
+
+ gridService.getGridEditors().then(function (response) {
+ $scope.availableEditors = response.data;
+
+ $scope.contentReady = true;
+
+ // *********************************************
+ // Init grid
+ // *********************************************
+ $scope.initContent();
+
+ });
+
+ //Clean the grid value before submitting to the server, we don't need
+ // all of that grid configuration in the value to be stored!! All of that
+ // needs to be merged in at runtime to ensure that the real config values are used
+ // if they are ever updated.
+
+ var unsubscribe = $scope.$on("formSubmitting", function () {
+
+ if ($scope.model.value && $scope.model.value.sections) {
+ _.each($scope.model.value.sections, function(section) {
+ if (section.rows) {
+ _.each(section.rows, function (row) {
+ if (row.areas) {
+ _.each(row.areas, function (area) {
+
+ //Remove the 'editors' - these are the allowed editors, these will
+ // be injected at runtime to this editor, it should not be persisted
+
+ if (area.editors) {
+ delete area.editors;
+ }
+
+ if (area.controls) {
+ _.each(area.controls, function (control) {
+ if (control.editor) {
+ //replace
+ var alias = control.editor.alias;
+ control.editor = {
+ alias: alias
+ };
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on("$destroy", function () {
+ unsubscribe();
+ });
+
+ });
+
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.GridPrevalueEditorController",
+ function ($scope, $http, assetsService, $rootScope, dialogService, mediaResource, gridService, imageHelper, $timeout) {
+
+ var emptyModel = {
+ styles:[
+ {
+ label: "Set a background image",
+ description: "Set a row background",
+ key: "background-image",
+ view: "imagepicker",
+ modifier: "url({0})"
+ }
+ ],
+
+ config:[
+ {
+ label: "Class",
+ description: "Set a css class",
+ key: "class",
+ view: "textstring"
+ }
+ ],
+
+ columns: 12,
+ templates:[
+ {
+ name: "1 column layout",
+ sections: [
+ {
+ grid: 12,
+ }
+ ]
+ },
+ {
+ name: "2 column layout",
+ sections: [
+ {
+ grid: 4,
+ },
+ {
+ grid: 8
+ }
+ ]
+ }
+ ],
+
+
+ layouts:[
+ {
+ label: "Headline",
+ name: "Headline",
+ areas: [
+ {
+ grid: 12,
+ editors: ["headline"]
+ }
+ ]
+ },
+ {
+ label: "Article",
+ name: "Article",
+ areas: [
+ {
+ grid: 4
+ },
+ {
+ grid: 8
+ }
+ ]
+ }
+ ]
+ };
+
+ /****************
+ template
+ *****************/
+
+ $scope.configureTemplate = function(template) {
+
+ var templatesCopy = angular.copy($scope.model.value.templates);
+
+ if (template === undefined) {
+ template = {
+ name: "",
+ sections: [
+
+ ]
+ };
+ $scope.model.value.templates.push(template);
+ }
+
+ $scope.layoutConfigOverlay = {};
+ $scope.layoutConfigOverlay.view = "views/propertyEditors/grid/dialogs/layoutconfig.html";
+ $scope.layoutConfigOverlay.currentLayout = template;
+ $scope.layoutConfigOverlay.rows = $scope.model.value.layouts;
+ $scope.layoutConfigOverlay.columns = $scope.model.value.columns;
+ $scope.layoutConfigOverlay.show = true;
+
+ $scope.layoutConfigOverlay.submit = function(model) {
+ $scope.layoutConfigOverlay.show = false;
+ $scope.layoutConfigOverlay = null;
+ };
+
+ $scope.layoutConfigOverlay.close = function(oldModel) {
+
+ //reset templates
+ $scope.model.value.templates = templatesCopy;
+
+ $scope.layoutConfigOverlay.show = false;
+ $scope.layoutConfigOverlay = null;
+ }
+
+ };
+
+ $scope.deleteTemplate = function(index){
+ $scope.model.value.templates.splice(index, 1);
+ };
+
+
+ /****************
+ Row
+ *****************/
+
+ $scope.configureLayout = function(layout) {
+
+ var layoutsCopy = angular.copy($scope.model.value.layouts);
+
+ if(layout === undefined){
+ layout = {
+ name: "",
+ areas:[
+
+ ]
+ };
+ $scope.model.value.layouts.push(layout);
+ }
+
+ $scope.rowConfigOverlay = {};
+ $scope.rowConfigOverlay.view = "views/propertyEditors/grid/dialogs/rowconfig.html";
+ $scope.rowConfigOverlay.currentRow = layout;
+ $scope.rowConfigOverlay.editors = $scope.editors;
+ $scope.rowConfigOverlay.columns = $scope.model.value.columns;
+ $scope.rowConfigOverlay.show = true;
+
+ $scope.rowConfigOverlay.submit = function(model) {
+ $scope.rowConfigOverlay.show = false;
+ $scope.rowConfigOverlay = null;
+ };
+
+ $scope.rowConfigOverlay.close = function(oldModel) {
+ $scope.model.value.layouts = layoutsCopy;
+ $scope.rowConfigOverlay.show = false;
+ $scope.rowConfigOverlay = null;
+ };
+
+ };
+
+ //var rowDeletesPending = false;
+ $scope.deleteLayout = function(index) {
+
+ $scope.rowDeleteOverlay = {};
+ $scope.rowDeleteOverlay.view = "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html";
+ $scope.rowDeleteOverlay.dialogData = {
+ rowName: $scope.model.value.layouts[index].name
+ };
+ $scope.rowDeleteOverlay.show = true;
+
+ $scope.rowDeleteOverlay.submit = function(model) {
+
+ $scope.model.value.layouts.splice(index, 1);
+
+ $scope.rowDeleteOverlay.show = false;
+ $scope.rowDeleteOverlay = null;
+ };
+
+ $scope.rowDeleteOverlay.close = function(oldModel) {
+ $scope.rowDeleteOverlay.show = false;
+ $scope.rowDeleteOverlay = null;
+ };
+
+ };
+
+
+ /****************
+ utillities
+ *****************/
+ $scope.toggleCollection = function(collection, toggle){
+ if(toggle){
+ collection = [];
+ }else{
+ delete collection;
+ }
+ };
+
+ $scope.percentage = function(spans){
+ return ((spans / $scope.model.value.columns) * 100).toFixed(8);
+ };
+
+ $scope.zeroWidthFilter = function (cell) {
+ return cell.grid > 0;
+ };
+
+ /****************
+ Config
+ *****************/
+
+ $scope.removeConfigValue = function(collection, index){
+ collection.splice(index, 1);
+ };
+
+ var editConfigCollection = function(configValues, title, callback) {
+
+ $scope.editConfigCollectionOverlay = {};
+ $scope.editConfigCollectionOverlay.view = "views/propertyeditors/grid/dialogs/editconfig.html";
+ $scope.editConfigCollectionOverlay.config = configValues;
+ $scope.editConfigCollectionOverlay.title = title;
+ $scope.editConfigCollectionOverlay.show = true;
+
+ $scope.editConfigCollectionOverlay.submit = function(model) {
+
+ callback(model.config)
+
+ $scope.editConfigCollectionOverlay.show = false;
+ $scope.editConfigCollectionOverlay = null;
+ };
+
+ $scope.editConfigCollectionOverlay.close = function(oldModel) {
+ $scope.editConfigCollectionOverlay.show = false;
+ $scope.editConfigCollectionOverlay = null;
+ };
+
+ };
+
+ $scope.editConfig = function() {
+ editConfigCollection($scope.model.value.config, "Settings", function(data) {
+ $scope.model.value.config = data;
+ });
+ };
+
+ $scope.editStyles = function() {
+ editConfigCollection($scope.model.value.styles, "Styling", function(data){
+ $scope.model.value.styles = data;
+ });
+ };
+
+ /****************
+ editors
+ *****************/
+ gridService.getGridEditors().then(function(response){
+ $scope.editors = response.data;
+ });
+
+
+ /* init grid data */
+ if (!$scope.model.value || $scope.model.value === "" || !$scope.model.value.templates) {
+ $scope.model.value = emptyModel;
+ } else {
+
+ if (!$scope.model.value.columns) {
+ $scope.model.value.columns = emptyModel.columns;
+ }
+
+
+ if (!$scope.model.value.config) {
+ $scope.model.value.config = [];
+ }
+
+ if (!$scope.model.value.styles) {
+ $scope.model.value.styles = [];
+ }
+ }
+
+ /****************
+ Clean up
+ *****************/
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ var ts = $scope.model.value.templates;
+ var ls = $scope.model.value.layouts;
+
+ _.each(ts, function(t){
+ _.each(t.sections, function(section, index){
+ if(section.grid === 0){
+ t.sections.splice(index, 1);
+ }
+ });
+ });
+
+ _.each(ls, function(l){
+ _.each(l.areas, function(area, index){
+ if(area.grid === 0){
+ l.areas.splice(index, 1);
+ }
+ });
+ });
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+ });
+
+//this controller simply tells the dialogs service to open a mediaPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+angular.module('umbraco')
+ .controller("Umbraco.PropertyEditors.ImageCropperController",
+ function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager, angularHelper) {
+
+ var config = angular.copy($scope.model.config);
+ $scope.imageIsLoaded = false;
+
+ //move previously saved value to the editor
+ if ($scope.model.value) {
+ //backwards compat with the old file upload (incase some-one swaps them..)
+ if (angular.isString($scope.model.value)) {
+ config.src = $scope.model.value;
+ $scope.model.value = config;
+ } else if ($scope.model.value.crops) {
+ //sync any config changes with the editor and drop outdated crops
+ _.each($scope.model.value.crops, function (saved) {
+ var configured = _.find(config.crops, function (item) { return item.alias === saved.alias });
+
+ if (configured && configured.height === saved.height && configured.width === saved.width) {
+ configured.coordinates = saved.coordinates;
+ }
+ });
+ $scope.model.value.crops = config.crops;
+
+ //restore focalpoint if missing
+ if (!$scope.model.value.focalPoint) {
+ $scope.model.value.focalPoint = { left: 0.5, top: 0.5 };
+ }
+ }
+
+ $scope.imageSrc = $scope.model.value.src;
+ }
+
+
+ //crop a specific crop
+ $scope.crop = function (crop) {
+ $scope.currentCrop = crop;
+ $scope.currentPoint = undefined;
+ };
+
+ //done cropping
+ $scope.done = function () {
+ $scope.currentCrop = undefined;
+ $scope.currentPoint = undefined;
+ };
+
+ //crop a specific crop
+ $scope.clear = function (crop) {
+ //clear current uploaded files
+ fileManager.setFiles($scope.model.alias, []);
+
+ //clear the ui
+ $scope.imageSrc = undefined;
+ if ($scope.model.value) {
+ delete $scope.model.value;
+ }
+
+ // set form to dirty to tricker discard changes dialog
+ var currForm = angularHelper.getCurrentForm($scope);
+ currForm.$setDirty();
+ };
+
+ //show previews
+ $scope.togglePreviews = function () {
+ if ($scope.showPreviews) {
+ $scope.showPreviews = false;
+ $scope.tempShowPreviews = false;
+ } else {
+ $scope.showPreviews = true;
+ }
+ };
+
+ $scope.imageLoaded = function() {
+ $scope.imageIsLoaded = true;
+ };
+
+ //on image selected, update the cropper
+ $scope.$on("filesSelected", function (ev, args) {
+ $scope.model.value = config;
+
+ if (args.files && args.files[0]) {
+
+ fileManager.setFiles($scope.model.alias, args.files);
+
+ var reader = new FileReader();
+ reader.onload = function (e) {
+
+ $scope.$apply(function () {
+ $scope.imageSrc = e.target.result;
+ });
+
+ };
+
+ reader.readAsDataURL(args.files[0]);
+ }
+ });
+
+
+ //here we declare a special method which will be called whenever the value has changed from the server
+ $scope.model.onValueChanged = function (newVal, oldVal) {
+ //clear current uploaded files
+ fileManager.setFiles($scope.model.alias, []);
+ };
+
+ var unsubscribe = $scope.$on("formSubmitting", function () {
+ $scope.done();
+ });
+
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+ })
+ .run(function (mediaHelper, umbRequestHelper) {
+ if (mediaHelper && mediaHelper.registerFileResolver) {
+
+ //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
+ // they contain different data structures so if we need to query against it we need to be aware of this.
+ mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) {
+ if (property.value && property.value.src) {
+
+ if (thumbnail === true) {
+ return property.value.src + "?width=500&mode=max&animationprocessmode=first";
+ }
+ else {
+ return property.value.src;
+ }
+
+ //this is a fallback in case the cropper has been asssigned a upload field
+ }
+ else if (angular.isString(property.value)) {
+ if (thumbnail) {
+
+ if (mediaHelper.detectIfImageByExtension(property.value)) {
+
+ var thumbnailUrl = umbRequestHelper.getApiUrl(
+ "imagesApiBaseUrl",
+ "GetBigThumbnail",
+ [{ originalImagePath: property.value }]);
+
+ return thumbnailUrl;
+ }
+ else {
+ return null;
+ }
+
+ }
+ else {
+ return property.value;
+ }
+ }
+
+ return null;
+ });
+ }
+ });
+
+angular.module("umbraco").controller("Umbraco.PrevalueEditors.CropSizesController",
+ function ($scope, $timeout) {
+
+ if (!$scope.model.value) {
+ $scope.model.value = [];
+ }
+
+ $scope.remove = function (item, evt) {
+ evt.preventDefault();
+ $scope.model.value = _.reject($scope.model.value, function (x) {
+ return x.alias === item.alias;
+ });
+ };
+
+ $scope.edit = function (item, evt) {
+ evt.preventDefault();
+ $scope.newItem = item;
+ };
+
+ $scope.cancel = function (evt) {
+ evt.preventDefault();
+ $scope.newItem = null;
+ };
+
+ $scope.add = function (evt) {
+ evt.preventDefault();
+
+ if ($scope.newItem && $scope.newItem.alias &&
+ angular.isNumber($scope.newItem.width) && angular.isNumber($scope.newItem.height) &&
+ $scope.newItem.width > 0 && $scope.newItem.height > 0) {
+
+ var exists = _.find($scope.model.value, function (item) { return $scope.newItem.alias === item.alias; });
+ if (!exists) {
+ $scope.model.value.push($scope.newItem);
+ $scope.newItem = {};
+ $scope.hasError = false;
+ return;
+ }
+ }
+
+ //there was an error, do the highlight (will be set back by the directive)
+ $scope.hasError = true;
+ };
+ });
+function includePropsPreValsController($rootScope, $scope, localizationService, contentTypeResource) {
+
+ if (!$scope.model.value) {
+ $scope.model.value = [];
+ }
+
+ $scope.propertyAliases = [];
+ $scope.selectedField = null;
+ $scope.systemFields = [
+ { value: "sortOrder" },
+ { value: "updateDate" },
+ { value: "updater" },
+ { value: "createDate" },
+ { value: "owner" },
+ { value: "published"},
+ { value: "contentTypeAlias" },
+ { value: "email" },
+ { value: "username" }
+ ];
+
+ $scope.getLocalizedKey = function(alias) {
+ switch (alias) {
+ case "name":
+ return "general_name";
+ case "sortOrder":
+ return "general_sort";
+ case "updateDate":
+ return "content_updateDate";
+ case "updater":
+ return "content_updatedBy";
+ case "createDate":
+ return "content_createDate";
+ case "owner":
+ return "content_createBy";
+ case "published":
+ return "content_isPublished";
+ case "contentTypeAlias":
+ //NOTE: This will just be 'Document' type even if it's for media/members since this is just a pre-val editor and we don't have a key for 'Content Type Alias'
+ return "content_documentType";
+ case "email":
+ return "general_email";
+ case "username":
+ return "general_username";
+ }
+ return alias;
+ }
+
+ $scope.removeField = function(e) {
+ $scope.model.value = _.reject($scope.model.value, function (x) {
+ return x.alias === e.alias;
+ });
+ }
+
+ //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
+ _.each($scope.systemFields, function (e, i) {
+ var key = $scope.getLocalizedKey(e.value);
+ localizationService.localize(key).then(function (v) {
+ e.name = v;
+
+ switch (e.value) {
+ case "updater":
+ e.name += " (Content only)";
+ break;
+ case "published":
+ e.name += " (Content only)";
+ break;
+ case "email":
+ e.name += " (Members only)";
+ break;
+ case "username":
+ e.name += " (Members only)";
+ break;
+ }
+
+ });
+ });
+
+ // Return a helper with preserved width of cells
+ var fixHelper = function (e, ui) {
+ var h = ui.clone();
+
+ h.children().each(function () {
+ $(this).width($(this).width());
+ });
+ h.css("background-color", "lightgray");
+
+ return h;
+ };
+
+ $scope.sortableOptions = {
+ helper: fixHelper,
+ handle: ".handle",
+ opacity: 0.5,
+ axis: 'y',
+ containment: 'parent',
+ cursor: 'move',
+ items: '> tr',
+ tolerance: 'pointer',
+ update: function (e, ui) {
+
+ // Get the new and old index for the moved element (using the text as the identifier)
+ var newIndex = ui.item.index();
+ var movedAlias = $('.alias-value', ui.item).text().trim();
+ var originalIndex = getAliasIndexByText(movedAlias);
+
+ // Move the element in the model
+ if (originalIndex > -1) {
+ var movedElement = $scope.model.value[originalIndex];
+ $scope.model.value.splice(originalIndex, 1);
+ $scope.model.value.splice(newIndex, 0, movedElement);
+ }
+ }
+ };
+
+ contentTypeResource.getAllPropertyTypeAliases().then(function(data) {
+ $scope.propertyAliases = data;
+ });
+
+ $scope.addField = function () {
+
+ var val = $scope.selectedField;
+ var isSystem = val.startsWith("_system_");
+ if (isSystem) {
+ val = val.trimStart("_system_");
+ }
+
+ var exists = _.find($scope.model.value, function (i) {
+ return i.alias === val;
+ });
+ if (!exists) {
+ $scope.model.value.push({
+ alias: val,
+ isSystem: isSystem ? 1 : 0
+ });
+ }
+ }
+
+ function getAliasIndexByText(value) {
+ for (var i = 0; i < $scope.model.value.length; i++) {
+ if ($scope.model.value[i].alias === value) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+}
+
+
+angular.module("umbraco").controller("Umbraco.PrevalueEditors.IncludePropertiesListViewController", includePropsPreValsController);
+/**
+ * @ngdoc controller
+ * @name Umbraco.PrevalueEditors.ListViewLayoutsPreValsController
+ * @function
+ *
+ * @description
+ * The controller for configuring layouts for list views
+ */
+(function() {
+ "use strict";
+
+ function ListViewLayoutsPreValsController($scope) {
+
+ var vm = this;
+ vm.focusLayoutName = false;
+
+ vm.layoutsSortableOptions = {
+ distance: 10,
+ tolerance: "pointer",
+ opacity: 0.7,
+ scroll: true,
+ cursor: "move",
+ handle: ".list-view-layout__sort-handle"
+ };
+
+ vm.addLayout = addLayout;
+ vm.showPrompt = showPrompt;
+ vm.hidePrompt = hidePrompt;
+ vm.removeLayout = removeLayout;
+ vm.openIconPicker = openIconPicker;
+
+ function activate() {
+
+
+
+ }
+
+ function addLayout() {
+
+ vm.focusLayoutName = false;
+
+ var layout = {
+ "name": "",
+ "path": "",
+ "icon": "icon-stop",
+ "selected": true
+ };
+
+ $scope.model.value.push(layout);
+
+ }
+
+ function showPrompt(layout) {
+ layout.deletePrompt = true;
+ }
+
+ function hidePrompt(layout) {
+ layout.deletePrompt = false;
+ }
+
+ function removeLayout($index, layout) {
+ $scope.model.value.splice($index, 1);
+ }
+
+ function openIconPicker(layout) {
+ vm.iconPickerDialog = {
+ view: "iconpicker",
+ show: true,
+ submit: function(model) {
+ if (model.color) {
+ layout.icon = model.icon + " " + model.color;
+ } else {
+ layout.icon = model.icon;
+ }
+ vm.focusLayoutName = true;
+ vm.iconPickerDialog.show = false;
+ vm.iconPickerDialog = null;
+ }
+ };
+ }
+
+ activate();
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.PrevalueEditors.ListViewLayoutsPreValsController", ListViewLayoutsPreValsController);
+
+})();
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.DocumentType.EditController
+ * @function
+ *
+ * @description
+ * The controller for the content type editor
+ */
+(function() {
+ "use strict";
+
+ function ListViewGridLayoutController($scope, $routeParams, mediaHelper, mediaResource, $location, listViewHelper) {
+
+ var vm = this;
+
+ vm.nodeId = $scope.contentId;
+ //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
+ vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles);
+ vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";
+ vm.activeDrag = false;
+ vm.mediaDetailsTooltip = {};
+ vm.itemsWithoutFolders = [];
+ vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20';
+
+ vm.dragEnter = dragEnter;
+ vm.dragLeave = dragLeave;
+ vm.onFilesQueue = onFilesQueue;
+ vm.onUploadComplete = onUploadComplete;
+
+ vm.hoverMediaItemDetails = hoverMediaItemDetails;
+ vm.selectContentItem = selectContentItem;
+ vm.selectItem = selectItem;
+ vm.selectFolder = selectFolder;
+ vm.goToItem = goToItem;
+
+ function activate() {
+ vm.itemsWithoutFolders = filterOutFolders($scope.items);
+ }
+
+ function filterOutFolders(items) {
+
+ var newArray = [];
+
+ if(items && items.length ) {
+
+ for (var i = 0; items.length > i; i++) {
+ var item = items[i];
+ var isFolder = !mediaHelper.hasFilePropertyType(item);
+
+ if (!isFolder) {
+ newArray.push(item);
+ }
+ }
+
+ }
+
+ return newArray;
+ }
+
+ function dragEnter(el, event) {
+ vm.activeDrag = true;
+ }
+
+ function dragLeave(el, event) {
+ vm.activeDrag = false;
+ }
+
+ function onFilesQueue() {
+ vm.activeDrag = false;
+ }
+
+ function onUploadComplete() {
+ $scope.getContent($scope.contentId);
+ }
+
+ function hoverMediaItemDetails(item, event, hover) {
+
+ if (hover && !vm.mediaDetailsTooltip.show) {
+
+ vm.mediaDetailsTooltip.event = event;
+ vm.mediaDetailsTooltip.item = item;
+ vm.mediaDetailsTooltip.show = true;
+
+ } else if (!hover && vm.mediaDetailsTooltip.show) {
+
+ vm.mediaDetailsTooltip.show = false;
+
+ }
+
+ }
+
+ function selectContentItem(item, $event, $index) {
+ listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event);
+ }
+
+ function selectItem(item, $event, $index) {
+ listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event);
+ }
+
+ function selectFolder(folder, $event, $index) {
+ listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event);
+ }
+
+ function goToItem(item, $event, $index) {
+ $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id);
+ }
+
+ activate();
+
+ }
+
+ angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.GridLayoutController", ListViewGridLayoutController);
+
+})();
+
+(function () {
+ "use strict";
+
+ function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper) {
+
+ var vm = this;
+
+ vm.nodeId = $scope.contentId;
+ //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
+ vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles);
+ vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";
+ vm.activeDrag = false;
+ vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20';
+
+ vm.selectItem = selectItem;
+ vm.clickItem = clickItem;
+ vm.selectAll = selectAll;
+ vm.isSelectedAll = isSelectedAll;
+ vm.isSortDirection = isSortDirection;
+ vm.sort = sort;
+ vm.dragEnter = dragEnter;
+ vm.dragLeave = dragLeave;
+ vm.onFilesQueue = onFilesQueue;
+ vm.onUploadComplete = onUploadComplete;
+
+ function selectAll($event) {
+ listViewHelper.selectAllItems($scope.items, $scope.selection, $event);
+ }
+
+ function isSelectedAll() {
+ return listViewHelper.isSelectedAll($scope.items, $scope.selection);
+ }
+
+ function selectItem(selectedItem, $index, $event) {
+ listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event);
+ }
+
+ function clickItem(item) {
+ // if item.id is 2147483647 (int.MaxValue) use item.key
+ $location.path($scope.entityType + '/' +$scope.entityType + '/edit/' + (item.id === 2147483647 ? item.key : item.id));
+ }
+
+ function isSortDirection(col, direction) {
+ return listViewHelper.setSortingDirection(col, direction, $scope.options);
+ }
+
+ function sort(field, allow, isSystem) {
+ if (allow) {
+ $scope.options.orderBySystemField = isSystem;
+ listViewHelper.setSorting(field, allow, $scope.options);
+ $scope.getContent($scope.contentId);
+ }
+ }
+
+ // Dropzone upload functions
+ function dragEnter(el, event) {
+ vm.activeDrag = true;
+ }
+
+ function dragLeave(el, event) {
+ vm.activeDrag = false;
+ }
+
+ function onFilesQueue() {
+ vm.activeDrag = false;
+ }
+
+ function onUploadComplete() {
+ $scope.getContent($scope.contentId);
+ }
+
+ }
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController);
+
+}) ();
+
+function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService, navigationService, treeService) {
+
+ //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
+ // that isn't created yet, if we continue this will use the parent id in the route params which isn't what
+ // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove
+ // the list view tab entirely when it's new.
+ if ($routeParams.create) {
+ $scope.isNew = true;
+ return;
+ }
+
+ //Now we need to check if this is for media, members or content because that will depend on the resources we use
+ var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback;
+
+ //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
+ if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) {
+ $scope.entityType = "member";
+ contentResource = $injector.get('memberResource');
+ getContentTypesCallback = $injector.get('memberTypeResource').getTypes;
+ getListResultsCallback = contentResource.getPagedResults;
+ deleteItemCallback = contentResource.deleteByKey;
+ getIdCallback = function (selected) {
+ var selectedKey = getItemKey(selected.id);
+ return selectedKey;
+ };
+ createEditUrlCallback = function (item) {
+ return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId;
+ };
+ }
+ else {
+ //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
+ if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) {
+ $scope.entityType = "media";
+ contentResource = $injector.get('mediaResource');
+ getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes;
+ }
+ else {
+ $scope.entityType = "content";
+ contentResource = $injector.get('contentResource');
+ getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes;
+ }
+ getListResultsCallback = contentResource.getChildren;
+ deleteItemCallback = contentResource.deleteById;
+ getIdCallback = function (selected) {
+ return selected.id;
+ };
+ createEditUrlCallback = function (item) {
+ return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber;
+ };
+ }
+
+ $scope.pagination = [];
+ $scope.isNew = false;
+ $scope.actionInProgress = false;
+ $scope.selection = [];
+ $scope.folders = [];
+ $scope.listViewResultSet = {
+ totalPages: 0,
+ items: []
+ };
+
+ $scope.currentNodePermissions = {}
+
+ //Just ensure we do have an editorState
+ if (editorState.current) {
+ //Fetch current node allowed actions for the current user
+ //This is the current node & not each individual child node in the list
+ var currentUserPermissions = editorState.current.allowedActions;
+
+ //Create a nicer model rather than the funky & hard to remember permissions strings
+ $scope.currentNodePermissions = {
+ "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O
+ "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C
+ "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D
+ "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M
+ "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U
+ "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish)
+ };
+ }
+
+ //when this is null, we don't check permissions
+ $scope.buttonPermissions = null;
+
+ //When we are dealing with 'content', we need to deal with permissions on child nodes.
+ // Currently there is no real good way to
+ if ($scope.entityType === "content") {
+
+ var idsWithPermissions = null;
+
+ $scope.buttonPermissions = {
+ canCopy: true,
+ canCreate: true,
+ canDelete: true,
+ canMove: true,
+ canPublish: true,
+ canUnpublish: true
+ };
+
+ $scope.$watch(function () {
+ return $scope.selection.length;
+ }, function (newVal, oldVal) {
+
+ if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) {
+
+ //get all of the selected ids
+ var ids = _.map($scope.selection, function (i) {
+ return i.id.toString();
+ });
+
+ //remove the dictionary items that don't have matching ids
+ var filtered = {};
+ _.each(idsWithPermissions, function (value, key, list) {
+ if (_.contains(ids, key)) {
+ filtered[key] = value;
+ }
+ });
+ idsWithPermissions = filtered;
+
+ //find all ids that we haven't looked up permissions for
+ var existingIds = _.keys(idsWithPermissions);
+ var missingLookup = _.map(_.difference(ids, existingIds), function (i) {
+ return Number(i);
+ });
+
+ if (missingLookup.length > 0) {
+ contentResource.getPermissions(missingLookup).then(function (p) {
+ $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions);
+ });
+ }
+ else {
+ $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions);
+ }
+ }
+ });
+
+ }
+
+ $scope.options = {
+ displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1,
+ pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10,
+ pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1,
+ filter: '',
+ orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(),
+ orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc",
+ orderBySystemField: true,
+ includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [
+ { alias: 'updateDate', header: 'Last edited', isSystem: 1 },
+ { alias: 'updater', header: 'Last edited by', isSystem: 1 }
+ ],
+ layout: {
+ layouts: $scope.model.config.layouts,
+ activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts)
+ },
+ allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish,
+ allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish,
+ allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy,
+ allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove,
+ allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete
+ };
+
+ // Check if selected order by field is actually custom field
+ for (var j = 0; j < $scope.options.includeProperties.length; j++) {
+ var includedProperty = $scope.options.includeProperties[j];
+ if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) {
+ $scope.options.orderBySystemField = includedProperty.isSystem === 1;
+ break;
+ }
+ }
+
+ //update all of the system includeProperties to enable sorting
+ _.each($scope.options.includeProperties, function (e, i) {
+
+ //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted
+ // to do that, we'd need to update the base query for content to include the content type alias column
+ // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff?
+ if (e.alias != "contentTypeAlias") {
+ e.allowSorting = true;
+ }
+
+ // Another special case for members, only fields on the base table (cmsMember) can be used for sorting
+ if (e.isSystem && $scope.entityType == "member") {
+ e.allowSorting = e.alias == 'username' || e.alias == 'email';
+ }
+
+ if (e.isSystem) {
+ //localize the header
+ var key = getLocalizedKey(e.alias);
+ localizationService.localize(key).then(function (v) {
+ e.header = v;
+ });
+ }
+ });
+
+ $scope.selectLayout = function (selectedLayout) {
+ $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts);
+ };
+
+ function showNotificationsAndReset(err, reload, successMsg) {
+
+ //check if response is ysod
+ if (err.status && err.status >= 500) {
+
+ // Open ysod overlay
+ $scope.ysodOverlay = {
+ view: "ysod",
+ error: err,
+ show: true
+ };
+ }
+
+ $timeout(function() {
+ $scope.bulkStatus = "";
+ $scope.actionInProgress = false;
+ },
+ 500);
+
+ if (reload === true) {
+ $scope.reloadView($scope.contentId);
+ }
+
+ if (err.data && angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ } else if (successMsg) {
+ localizationService.localize("bulk_done")
+ .then(function(v) {
+ notificationsService.success(v, successMsg);
+ });
+ }
+ }
+
+ $scope.next = function (pageNumber) {
+ $scope.options.pageNumber = pageNumber;
+ $scope.reloadView($scope.contentId);
+ };
+
+ $scope.goToPage = function (pageNumber) {
+ $scope.options.pageNumber = pageNumber;
+ $scope.reloadView($scope.contentId);
+ };
+
+ $scope.prev = function (pageNumber) {
+ $scope.options.pageNumber = pageNumber;
+ $scope.reloadView($scope.contentId);
+ };
+
+
+ /*Loads the search results, based on parameters set in prev,next,sort and so on*/
+ /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state
+ with simple values */
+
+ $scope.reloadView = function (id) {
+
+ $scope.viewLoaded = false;
+
+ listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
+
+ getListResultsCallback(id, $scope.options).then(function (data) {
+
+ $scope.actionInProgress = false;
+ $scope.listViewResultSet = data;
+
+ //update all values for display
+ if ($scope.listViewResultSet.items) {
+ _.each($scope.listViewResultSet.items, function (e, index) {
+ setPropertyValues(e);
+ });
+ }
+
+ if ($scope.entityType === 'media') {
+
+ mediaResource.getChildFolders($scope.contentId)
+ .then(function (folders) {
+ $scope.folders = folders;
+ $scope.viewLoaded = true;
+ });
+
+ } else {
+ $scope.viewLoaded = true;
+ }
+
+ //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example
+ // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last
+ // available page and then re-load again
+ if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
+ $scope.options.pageNumber = $scope.listViewResultSet.totalPages;
+
+ //reload!
+ $scope.reloadView(id);
+ }
+
+ });
+ };
+
+ var searchListView = _.debounce(function () {
+ $scope.$apply(function () {
+ makeSearch();
+ });
+ }, 500);
+
+ $scope.forceSearch = function (ev) {
+ //13: enter
+ switch (ev.keyCode) {
+ case 13:
+ makeSearch();
+ break;
+ }
+ };
+
+ $scope.enterSearch = function () {
+ $scope.viewLoaded = false;
+ searchListView();
+ };
+
+ function makeSearch() {
+ if ($scope.options.filter !== null && $scope.options.filter !== undefined) {
+ $scope.options.pageNumber = 1;
+ //$scope.actionInProgress = true;
+ $scope.reloadView($scope.contentId);
+ }
+ }
+
+ $scope.isAnythingSelected = function () {
+ if ($scope.selection.length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ $scope.selectedItemsCount = function () {
+ return $scope.selection.length;
+ };
+
+ $scope.clearSelection = function () {
+ listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
+ };
+
+ $scope.getIcon = function (entry) {
+ return iconHelper.convertFromLegacyIcon(entry.icon);
+ };
+
+ function serial(selected, fn, getStatusMsg, index) {
+ return fn(selected, index).then(function (content) {
+ index++;
+ $scope.bulkStatus = getStatusMsg(index, selected.length);
+ return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content;
+ }, function (err) {
+ var reload = index > 0;
+ showNotificationsAndReset(err, reload);
+ return err;
+ });
+ }
+
+ function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) {
+ var selected = $scope.selection;
+ if (selected.length === 0)
+ return;
+ if (confirmMsg && !confirm(confirmMsg))
+ return;
+
+ $scope.actionInProgress = true;
+ $scope.bulkStatus = getStatusMsg(0, selected.length);
+
+ return serial(selected, fn, getStatusMsg, 0).then(function (result) {
+ // executes once the whole selection has been processed
+ // in case of an error (caught by serial), result will be the error
+ if (!(result.data && angular.isArray(result.data.notifications)))
+ showNotificationsAndReset(result, true, getSuccessMsg(selected.length));
+ });
+ }
+
+ $scope.delete = function() {
+ var confirmDeleteText = "";
+
+ localizationService.localize("defaultdialogs_confirmdelete")
+ .then(function(value) {
+ confirmDeleteText = value;
+
+ var attempt =
+ applySelected(
+ function(selected, index) { return deleteItemCallback(getIdCallback(selected[index])); },
+ function(count, total) {
+ var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems");
+ return localizationService.localize(key, [count, total]);
+ },
+ function(total) {
+ var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems");
+ return localizationService.localize(key, [total]);
+ },
+ confirmDeleteText + "?");
+ if (attempt) {
+ attempt.then(function() {
+ //executes if all is successful, let's sync the tree
+ var activeNode = appState.getTreeState("selectedNode");
+ if (activeNode) {
+ navigationService.reloadNode(activeNode);
+ }
+ });
+ }
+ });
+ };
+
+ $scope.publish = function () {
+ applySelected(
+ function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); },
+ function (count, total) {
+ var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems");
+ return localizationService.localize(key, [count, total]);
+ },
+ function (total) {
+ var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems");
+ return localizationService.localize(key, [total]);
+ });
+ };
+
+ $scope.unpublish = function() {
+ applySelected(
+ function(selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); },
+ function(count, total) {
+ var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems");
+ return localizationService.localize(key, [count, total]);
+ },
+ function(total) {
+ var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems");
+ return localizationService.localize(key, [total]);
+ });
+ };
+
+ $scope.move = function() {
+ $scope.moveDialog = {};
+ $scope.moveDialog.title = localizationService.localize("general_move");
+ $scope.moveDialog.section = $scope.entityType;
+ $scope.moveDialog.currentNode = $scope.contentId;
+ $scope.moveDialog.view = "move";
+ $scope.moveDialog.show = true;
+
+ $scope.moveDialog.submit = function(model) {
+
+ if (model.target) {
+ performMove(model.target);
+ }
+
+ $scope.moveDialog.show = false;
+ $scope.moveDialog = null;
+ };
+
+ $scope.moveDialog.close = function(oldModel) {
+ $scope.moveDialog.show = false;
+ $scope.moveDialog = null;
+ };
+
+ };
+
+
+ function performMove(target) {
+
+ //NOTE: With the way this applySelected/serial works, I'm not sure there's a better way currently to return
+ // a specific value from one of the methods, so we'll have to try this way. Even though the first method
+ // will fire once per every node moved, the destination path will be the same and we need to use that to sync.
+ var newPath = null;
+ applySelected(
+ function(selected, index) {
+ return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) })
+ .then(function(path) {
+ newPath = path;
+ return path;
+ });
+ },
+ function(count, total) {
+ var key = (total === 1 ? "bulk_movedItemOfItem" : "bulk_movedItemOfItems");
+ return localizationService.localize(key, [count, total]);
+ },
+ function(total) {
+ var key = (total === 1 ? "bulk_movedItem" : "bulk_movedItems");
+ return localizationService.localize(key, [total]);
+ })
+ .then(function() {
+ //executes if all is successful, let's sync the tree
+ if (newPath) {
+
+ //we need to do a double sync here: first refresh the node where the content was moved,
+ // then refresh the node where the content was moved from
+ navigationService.syncTree({
+ tree: target.nodeType,
+ path: newPath,
+ forceReload: true,
+ activate: false
+ })
+ .then(function(args) {
+ //get the currently edited node (if any)
+ var activeNode = appState.getTreeState("selectedNode");
+ if (activeNode) {
+ navigationService.reloadNode(activeNode);
+ }
+ });
+ }
+ });
+ }
+
+ $scope.copy = function () {
+ $scope.copyDialog = {};
+ $scope.copyDialog.title = localizationService.localize("general_copy");
+ $scope.copyDialog.section = $scope.entityType;
+ $scope.copyDialog.currentNode = $scope.contentId;
+ $scope.copyDialog.view = "copy";
+ $scope.copyDialog.show = true;
+
+ $scope.copyDialog.submit = function (model) {
+ if (model.target) {
+ performCopy(model.target, model.relateToOriginal);
+ }
+
+ $scope.copyDialog.show = false;
+ $scope.copyDialog = null;
+ };
+
+ $scope.copyDialog.close = function (oldModel) {
+ $scope.copyDialog.show = false;
+ $scope.copyDialog = null;
+ };
+
+ };
+
+ function performCopy(target, relateToOriginal) {
+ applySelected(
+ function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); },
+ function (count, total) {
+ var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems");
+ return localizationService.localize(key, [count, total]);
+ },
+ function (total) {
+ var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems");
+ return localizationService.localize(key, [total]);
+ });
+ }
+
+ function getCustomPropertyValue(alias, properties) {
+ var value = '';
+ var index = 0;
+ var foundAlias = false;
+ for (var i = 0; i < properties.length; i++) {
+ if (properties[i].alias == alias) {
+ foundAlias = true;
+ break;
+ }
+ index++;
+ }
+
+ if (foundAlias) {
+ value = properties[index].value;
+ }
+
+ return value;
+ }
+
+ /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */
+ function setPropertyValues(result) {
+
+ //set the edit url
+ result.editPath = createEditUrlCallback(result);
+
+ _.each($scope.options.includeProperties, function (e, i) {
+
+ var alias = e.alias;
+
+ // First try to pull the value directly from the alias (e.g. updatedBy)
+ var value = result[alias];
+
+ // If this returns an object, look for the name property of that (e.g. owner.name)
+ if (value === Object(value)) {
+ value = value['name'];
+ }
+
+ // If we've got nothing yet, look at a user defined property
+ if (typeof value === 'undefined') {
+ value = getCustomPropertyValue(alias, result.properties);
+ }
+
+ // If we have a date, format it
+ if (isDate(value)) {
+ value = value.substring(0, value.length - 3);
+ }
+
+ // set what we've got on the result
+ result[alias] = value;
+ });
+
+
+ }
+
+ function isDate(val) {
+ if (angular.isString(val)) {
+ return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/);
+ }
+ return false;
+ }
+
+ function initView() {
+ //default to root id if the id is undefined
+ var id = $routeParams.id;
+ if (id === undefined) {
+ id = -1;
+ }
+
+ $scope.listViewAllowedTypes = getContentTypesCallback(id);
+
+ $scope.contentId = id;
+ $scope.isTrashed = id === "-20" || id === "-21";
+
+ $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed;
+ $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed;
+
+ $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish ||
+ $scope.options.allowBulkUnpublish ||
+ $scope.options.allowBulkCopy ||
+ $scope.options.allowBulkMove ||
+ $scope.options.allowBulkDelete;
+
+ $scope.reloadView($scope.contentId);
+ }
+
+ function getLocalizedKey(alias) {
+
+ switch (alias) {
+ case "sortOrder":
+ return "general_sort";
+ case "updateDate":
+ return "content_updateDate";
+ case "updater":
+ return "content_updatedBy";
+ case "createDate":
+ return "content_createDate";
+ case "owner":
+ return "content_createBy";
+ case "published":
+ return "content_isPublished";
+ case "contentTypeAlias":
+ //TODO: Check for members
+ return $scope.entityType === "content" ? "content_documentType" : "content_mediatype";
+ case "email":
+ return "general_email";
+ case "username":
+ return "general_username";
+ }
+ return alias;
+ }
+
+ function getItemKey(itemId) {
+ for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
+ var item = $scope.listViewResultSet.items[i];
+ if (item.id === itemId) {
+ return item.key;
+ }
+ }
+ }
+
+ //GO!
+ initView();
+}
+
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController);
+
+function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) {
+ //Get the prevalue from the correct place
+ function getPrevalues() {
+ if (editorState.current.preValues) {
+ return editorState.current.preValues;
+ }
+ else {
+ return listViewPrevalueHelper.getPrevalues();
+ }
+ }
+
+ //Watch the prevalues
+ $scope.$watch(function () {
+ return _.findWhere(getPrevalues(), { key: "includeProperties" }).value;
+ }, function () {
+ populateFields();
+ }, true); //Use deep watching, otherwise we won't pick up header changes
+
+ function populateFields() {
+ // Helper to find a particular value from the list of sort by options
+ function findFromSortByFields(value) {
+ return _.find($scope.sortByFields, function (e) {
+ return e.value.toLowerCase() === value.toLowerCase();
+ });
+ }
+
+ // Get list of properties assigned as columns of the list view
+ var propsPreValue = _.findWhere(getPrevalues(), { key: "includeProperties" });
+
+ // Populate list of options for the default sort (all the columns plus then node name)
+ $scope.sortByFields = [];
+ $scope.sortByFields.push({ value: "name", name: "Name", isSystem: 1 });
+ if (propsPreValue != undefined) {
+ for (var i = 0; i < propsPreValue.value.length; i++) {
+ var value = propsPreValue.value[i];
+ $scope.sortByFields.push({
+ value: value.alias,
+ name: value.header,
+ isSystem: value.isSystem
+ });
+ }
+ }
+
+ // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared
+ var systemFields = [
+ { value: "SortOrder", key: "general_sort" },
+ { value: "Name", key: "general_name" },
+ { value: "VersionDate", key: "content_updateDate" },
+ { value: "Updater", key: "content_updatedBy" },
+ { value: "CreateDate", key: "content_createDate" },
+ { value: "Owner", key: "content_createBy" },
+ { value: "ContentTypeAlias", key: "content_documentType" },
+ { value: "Published", key: "content_isPublished" },
+ { value: "Email", key: "general_email" },
+ { value: "Username", key: "general_username" }
+ ];
+ _.each(systemFields, function (e) {
+ localizationService.localize(e.key).then(function (v) {
+
+ var sortByListValue = findFromSortByFields(e.value);
+ if (sortByListValue) {
+ sortByListValue.name = v;
+ switch (e.value) {
+ case "Updater":
+ e.name += " (Content only)";
+ break;
+ case "Published":
+ e.name += " (Content only)";
+ break;
+ case "Email":
+ e.name += " (Members only)";
+ break;
+ case "Username":
+ e.name += " (Members only)";
+ break;
+ }
+ }
+ });
+ });
+
+ // Check existing model value is available in list and ensure a value is set
+ var existingValue = findFromSortByFields($scope.model.value);
+ if (existingValue) {
+ // Set the existing value
+ // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set
+ $scope.model.value = existingValue.value;
+ }
+ else {
+ // Existing value not found, set to first value
+ $scope.model.value = $scope.sortByFields[0].value;
+ }
+ }
+}
+
+
+angular.module("umbraco").controller("Umbraco.PrevalueEditors.SortByListViewController", sortByPreValsController);
+//DO NOT DELETE THIS, this is in use...
+angular.module('umbraco')
+.controller("Umbraco.PropertyEditors.MacroContainerController",
+
+ function($scope, dialogService, entityResource, macroService){
+ $scope.renderModel = [];
+
+ if($scope.model.value){
+ var macros = $scope.model.value.split('>');
+
+ angular.forEach(macros, function(syntax, key){
+ if(syntax && syntax.length > 10){
+ //re-add the char we split on
+ syntax = syntax + ">";
+ var parsed = macroService.parseMacroSyntax(syntax);
+ if(!parsed){
+ parsed = {};
+ }
+
+ parsed.syntax = syntax;
+ collectDetails(parsed);
+ $scope.renderModel.push(parsed);
+ }
+ });
+ }
+
+
+ function collectDetails(macro){
+ macro.details = "";
+ if(macro.macroParamsDictionary){
+ angular.forEach((macro.macroParamsDictionary), function(value, key){
+ macro.details += key + ": " + value + " ";
+ });
+ }
+ }
+
+ function openDialog(index){
+ var dialogData = {
+ allowedMacros: $scope.model.config.allowed
+ };
+
+ if(index !== null && $scope.renderModel[index]) {
+ var macro = $scope.renderModel[index];
+ dialogData["macroData"] = macro;
+ }
+
+ $scope.macroPickerOverlay = {};
+ $scope.macroPickerOverlay.view = "macropicker";
+ $scope.macroPickerOverlay.dialogData = dialogData;
+ $scope.macroPickerOverlay.show = true;
+
+ $scope.macroPickerOverlay.submit = function(model) {
+
+ var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
+ collectDetails(macroObject);
+
+ //update the raw syntax and the list...
+ if(index !== null && $scope.renderModel[index]) {
+ $scope.renderModel[index] = macroObject;
+ } else {
+ $scope.renderModel.push(macroObject);
+ }
+
+ $scope.macroPickerOverlay.show = false;
+ $scope.macroPickerOverlay = null;
+ };
+
+ $scope.macroPickerOverlay.close = function(oldModel) {
+ $scope.macroPickerOverlay.show = false;
+ $scope.macroPickerOverlay = null;
+ };
+
+ }
+
+
+
+ $scope.edit =function(index){
+ openDialog(index);
+ };
+
+ $scope.add = function () {
+
+ if ($scope.model.config.max && $scope.model.config.max > 0 && $scope.renderModel.length >= $scope.model.config.max) {
+ //cannot add more than the max
+ return;
+ }
+
+ openDialog();
+ };
+
+ $scope.remove =function(index){
+ $scope.renderModel.splice(index, 1);
+ };
+
+ $scope.clear = function() {
+ $scope.model.value = "";
+ $scope.renderModel = [];
+ };
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ var syntax = [];
+ angular.forEach($scope.renderModel, function(value, key){
+ syntax.push(value.syntax);
+ });
+
+ $scope.model.value = syntax.join("");
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+
+ function trim(str, chr) {
+ var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
+ return str.replace(rgxtrim, '');
+ }
+
+});
+
+function MacroListController($scope, entityResource) {
+
+ $scope.items = [];
+
+ entityResource.getAll("Macro").then(function(items) {
+ _.each(items, function(i) {
+ $scope.items.push({ name: i.name, alias: i.alias });
+ });
+
+ });
+
+
+}
+
+angular.module("umbraco").controller("Umbraco.PrevalueEditors.MacroList", MacroListController);
+
+//inject umbracos assetsServce and dialog service
+function MarkdownEditorController($scope, $element, assetsService, dialogService, angularHelper, $timeout) {
+
+ //tell the assets service to load the markdown.editor libs from the markdown editors
+ //plugin folder
+
+ if ($scope.model.value === null || $scope.model.value === "") {
+ $scope.model.value = $scope.model.config.defaultValue;
+ }
+
+ function openMediaPicker(callback) {
+
+ $scope.mediaPickerOverlay = {};
+ $scope.mediaPickerOverlay.view = "mediaPicker";
+ $scope.mediaPickerOverlay.show = true;
+ $scope.mediaPickerOverlay.disableFolderSelect = true;
+
+ $scope.mediaPickerOverlay.submit = function(model) {
+
+ var selectedImagePath = model.selectedImages[0].image;
+ callback(selectedImagePath);
+
+ $scope.mediaPickerOverlay.show = false;
+ $scope.mediaPickerOverlay = null;
+ };
+
+ $scope.mediaPickerOverlay.close = function(model) {
+ $scope.mediaPickerOverlay.show = false;
+ $scope.mediaPickerOverlay = null;
+ };
+
+ }
+
+ assetsService
+ .load([
+ "lib/markdown/markdown.converter.js",
+ "lib/markdown/markdown.sanitizer.js",
+ "lib/markdown/markdown.editor.js"
+ ])
+ .then(function () {
+
+ // we need a short delay to wait for the textbox to appear.
+ setTimeout(function () {
+ //this function will execute when all dependencies have loaded
+ // but in the case that they've been previously loaded, we can only
+ // init the md editor after this digest because the DOM needs to be ready first
+ // so run the init on a timeout
+ $timeout(function () {
+ var converter2 = new Markdown.Converter();
+ var editor2 = new Markdown.Editor(converter2, "-" + $scope.model.alias);
+ editor2.run();
+
+ //subscribe to the image dialog clicks
+ editor2.hooks.set("insertImageDialog", function (callback) {
+ openMediaPicker(callback);
+ return true; // tell the editor that we'll take care of getting the image url
+ });
+
+ editor2.hooks.set("onPreviewRefresh", function () {
+ // We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library.
+ if ($scope.model.value !== $("textarea", $element).val()) {
+ angularHelper.getCurrentForm($scope).$setDirty();
+ $scope.model.value = $("textarea", $element).val();
+ }
+ });
+
+ }, 200);
+ });
+
+ //load the seperat css for the editor to avoid it blocking our js loading TEMP HACK
+ assetsService.loadCss("lib/markdown/markdown.css");
+ })
+}
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.MarkdownEditorController", MarkdownEditorController);
+
+//this controller simply tells the dialogs service to open a mediaPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController",
+ function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location) {
+
+ //check the pre-values for multi-picker
+ var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
+ var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false;
+ var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false;
+
+ if (!$scope.model.config.startNodeId) {
+ userService.getCurrentUser().then(function (userData) {
+ $scope.model.config.startNodeId = userData.startMediaId;
+ });
+ }
+
+ function setupViewModel() {
+ $scope.images = [];
+ $scope.ids = [];
+
+ if ($scope.model.value) {
+ var ids = $scope.model.value.split(',');
+
+ //NOTE: We need to use the entityResource NOT the mediaResource here because
+ // the mediaResource has server side auth configured for which the user must have
+ // access to the media section, if they don't they'll get auth errors. The entityResource
+ // acts differently in that it allows access if the user has access to any of the apps that
+ // might require it's use. Therefore we need to use the metatData property to get at the thumbnail
+ // value.
+
+ entityResource.getByIds(ids, "Media").then(function (medias) {
+
+ _.each(medias, function (media, i) {
+
+ //only show non-trashed items
+ if (media.parentId >= -1) {
+
+ if (!media.thumbnail) {
+ media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
+ }
+
+ $scope.images.push(media);
+ $scope.ids.push(media.id);
+ }
+ });
+
+ $scope.sync();
+ });
+ }
+ }
+
+ setupViewModel();
+
+ $scope.remove = function(index) {
+ $scope.images.splice(index, 1);
+ $scope.ids.splice(index, 1);
+ $scope.sync();
+ };
+
+ $scope.goToItem = function(item) {
+ $location.path('media/media/edit/' + item.id);
+ };
+
+ $scope.add = function() {
+
+ $scope.mediaPickerOverlay = {
+ view: "mediapicker",
+ title: "Select media",
+ startNodeId: $scope.model.config.startNodeId,
+ multiPicker: multiPicker,
+ onlyImages: onlyImages,
+ disableFolderSelect: disableFolderSelect,
+ show: true,
+ submit: function(model) {
+
+ _.each(model.selectedImages, function(media, i) {
+
+ if (!media.thumbnail) {
+ media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
+ }
+
+ $scope.images.push(media);
+ $scope.ids.push(media.id);
+ });
+
+ $scope.sync();
+
+ $scope.mediaPickerOverlay.show = false;
+ $scope.mediaPickerOverlay = null;
+
+ }
+ };
+
+ };
+
+ $scope.sortableOptions = {
+ update: function(e, ui) {
+ var r = [];
+ //TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the
+ // content picker. THen we don't have to worry about setting ids, render models, models, we just set one and let the
+ // watch do all the rest.
+ $timeout(function(){
+ angular.forEach($scope.images, function(value, key){
+ r.push(value.id);
+ });
+
+ $scope.ids = r;
+ $scope.sync();
+ }, 500, false);
+ }
+ };
+
+ $scope.sync = function() {
+ $scope.model.value = $scope.ids.join();
+ };
+
+ $scope.showAdd = function () {
+ if (!multiPicker) {
+ if ($scope.model.value && $scope.model.value !== "") {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ //here we declare a special method which will be called whenever the value has changed from the server
+ //this is instead of doing a watch on the model.value = faster
+ $scope.model.onValueChanged = function (newVal, oldVal) {
+ //update the display val again if it has changed from the server
+ setupViewModel();
+ };
+
+ });
+
+//this controller simply tells the dialogs service to open a memberPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+function memberGroupPicker($scope, dialogService){
+
+ function trim(str, chr) {
+ var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
+ return str.replace(rgxtrim, '');
+ }
+
+ $scope.renderModel = [];
+
+ if ($scope.model.value) {
+ var modelIds = $scope.model.value.split(',');
+ _.each(modelIds, function (item, i) {
+ $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' });
+ });
+ }
+
+ $scope.openMemberGroupPicker = function() {
+
+ $scope.memberGroupPicker = {};
+ $scope.memberGroupPicker.multiPicker = true;
+ $scope.memberGroupPicker.view = "memberGroupPicker";
+ $scope.memberGroupPicker.show = true;
+
+ $scope.memberGroupPicker.submit = function(model) {
+
+ if(model.selectedMemberGroups) {
+ _.each(model.selectedMemberGroups, function (item, i) {
+ $scope.add(item);
+ });
+ }
+
+ if(model.selectedMemberGroup) {
+ $scope.clear();
+ $scope.add(model.selectedMemberGroup);
+ }
+
+ $scope.memberGroupPicker.show = false;
+ $scope.memberGroupPicker = null;
+ };
+
+ $scope.memberGroupPicker.close = function(oldModel) {
+ $scope.memberGroupPicker.show = false;
+ $scope.memberGroupPicker = null;
+ };
+
+ };
+
+ $scope.remove =function(index){
+ $scope.renderModel.splice(index, 1);
+ };
+
+ $scope.add = function (item) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+
+ if (currIds.indexOf(item) < 0) {
+ $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' });
+ }
+ };
+
+ $scope.clear = function() {
+ $scope.renderModel = [];
+ };
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+ $scope.model.value = trim(currIds.join(), ",");
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+}
+
+angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupPickerController", memberGroupPicker);
+
+function memberGroupController($rootScope, $scope, dialogService, mediaResource, imageHelper, $log) {
+
+ //set the available to the keys of the dictionary who's value is true
+ $scope.getAvailable = function () {
+ var available = [];
+ for (var n in $scope.model.value) {
+ if ($scope.model.value[n] === false) {
+ available.push(n);
+ }
+ }
+ return available;
+ };
+ //set the selected to the keys of the dictionary who's value is true
+ $scope.getSelected = function () {
+ var selected = [];
+ for (var n in $scope.model.value) {
+ if ($scope.model.value[n] === true) {
+ selected.push(n);
+ }
+ }
+ return selected;
+ };
+
+ $scope.addItem = function(item) {
+ //keep the model up to date
+ $scope.model.value[item] = true;
+ };
+
+ $scope.removeItem = function (item) {
+ //keep the model up to date
+ $scope.model.value[item] = false;
+ };
+
+
+}
+angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupController", memberGroupController);
+//this controller simply tells the dialogs service to open a memberPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+function memberPickerController($scope, dialogService, entityResource, $log, iconHelper, angularHelper){
+
+ function trim(str, chr) {
+ var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
+ return str.replace(rgxtrim, '');
+ }
+
+ $scope.renderModel = [];
+
+ var dialogOptions = {
+ multiPicker: false,
+ entityType: "Member",
+ section: "member",
+ treeAlias: "member",
+ filter: function(i) {
+ return i.metaData.isContainer == true;
+ },
+ filterCssClass: "not-allowed",
+ callback: function(data) {
+ if (angular.isArray(data)) {
+ _.each(data, function (item, i) {
+ $scope.add(item);
+ });
+ } else {
+ $scope.clear();
+ $scope.add(data);
+ }
+ angularHelper.getCurrentForm($scope).$setDirty();
+ }
+ };
+
+ //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
+ // pre-value config on to the dialog options
+ if ($scope.model.config) {
+ angular.extend(dialogOptions, $scope.model.config);
+ }
+
+ $scope.openMemberPicker = function() {
+ $scope.memberPickerOverlay = dialogOptions;
+ $scope.memberPickerOverlay.view = "memberPicker";
+ $scope.memberPickerOverlay.show = true;
+
+ $scope.memberPickerOverlay.submit = function(model) {
+
+ if (model.selection) {
+ _.each(model.selection, function(item, i) {
+ $scope.add(item);
+ });
+ }
+
+ $scope.memberPickerOverlay.show = false;
+ $scope.memberPickerOverlay = null;
+ };
+
+ $scope.memberPickerOverlay.close = function(oldModel) {
+ $scope.memberPickerOverlay.show = false;
+ $scope.memberPickerOverlay = null;
+ };
+
+ };
+
+ $scope.remove =function(index){
+ $scope.renderModel.splice(index, 1);
+ };
+
+ $scope.add = function (item) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+
+ if (currIds.indexOf(item.id) < 0) {
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
+ }
+ };
+
+ $scope.clear = function() {
+ $scope.renderModel = [];
+ };
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+ var currIds = _.map($scope.renderModel, function (i) {
+ return i.id;
+ });
+ $scope.model.value = trim(currIds.join(), ",");
+ });
+
+ //when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+ //load member data
+ var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
+ entityResource.getByIds(modelIds, "Member").then(function (data) {
+ _.each(data, function (item, i) {
+ item.icon = iconHelper.convertFromLegacyIcon(item.icon);
+ $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
+ });
+ });
+}
+
+
+angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberPickerController", memberPickerController);
+
+function MultipleTextBoxController($scope) {
+
+ $scope.sortableOptions = {
+ axis: 'y',
+ containment: 'parent',
+ cursor: 'move',
+ items: '> div.control-group',
+ tolerance: 'pointer'
+ };
+
+ if (!$scope.model.value) {
+ $scope.model.value = [];
+ }
+
+ //add any fields that there isn't values for
+ if ($scope.model.config.min > 0) {
+ for (var i = 0; i < $scope.model.config.min; i++) {
+ if ((i + 1) > $scope.model.value.length) {
+ $scope.model.value.push({ value: "" });
+ }
+ }
+ }
+
+ $scope.add = function () {
+ if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) {
+ $scope.model.value.push({ value: "" });
+ }
+ };
+
+ $scope.remove = function(index) {
+ var remainder = [];
+ for (var x = 0; x < $scope.model.value.length; x++) {
+ if (x !== index) {
+ remainder.push($scope.model.value[x]);
+ }
+ }
+ $scope.model.value = remainder;
+ };
+
+}
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController);
+
+angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsController",
+ function($scope) {
+
+ if (angular.isObject($scope.model.config.items)) {
+
+ //now we need to format the items in the dictionary because we always want to have an array
+ var newItems = [];
+ var vals = _.values($scope.model.config.items);
+ var keys = _.keys($scope.model.config.items);
+ for (var i = 0; i < vals.length; i++) {
+ newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value });
+ }
+
+ //ensure the items are sorted by the provided sort order
+ newItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
+
+ //re-assign
+ $scope.model.config.items = newItems;
+
+ }
+
+ });
+
+/**
+ * @ngdoc controller
+ * @name Umbraco.Editors.ReadOnlyValueController
+ * @function
+ *
+ * @description
+ * The controller for the readonlyvalue property editor.
+ * This controller offer more functionality than just a simple label as it will be able to apply formatting to the
+ * value to be displayed. This means that we also have to apply more complex logic of watching the model value when
+ * it changes because we are creating a new scope value called displayvalue which will never change based on the server data.
+ * In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of
+ * readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here.
+*/
+function ReadOnlyValueController($rootScope, $scope, $filter) {
+
+ function formatDisplayValue() {
+
+ if ($scope.model.config &&
+ angular.isArray($scope.model.config) &&
+ $scope.model.config.length > 0 &&
+ $scope.model.config[0] &&
+ $scope.model.config.filter) {
+
+ if ($scope.model.config.format) {
+ $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format);
+ } else {
+ $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value);
+ }
+ } else {
+ $scope.displayvalue = $scope.model.value;
+ }
+
+ }
+
+ //format the display value on init:
+ formatDisplayValue();
+
+ $scope.$watch("model.value", function (newVal, oldVal) {
+ //cannot just check for !newVal because it might be an empty string which we
+ //want to look for.
+ if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
+ //update the display val again
+ formatDisplayValue();
+ }
+ });
+}
+
+angular.module('umbraco').controller("Umbraco.PropertyEditors.ReadOnlyValueController", ReadOnlyValueController);
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.RelatedLinksController",
+ function ($rootScope, $scope, dialogService, iconHelper) {
+
+ if (!$scope.model.value) {
+ $scope.model.value = [];
+ }
+
+ $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE;
+
+ $scope.newCaption = '';
+ $scope.newLink = 'http://';
+ $scope.newNewWindow = false;
+ $scope.newInternal = null;
+ $scope.newInternalName = '';
+ $scope.newInternalIcon = null;
+ $scope.addExternal = true;
+ $scope.currentEditLink = null;
+ $scope.hasError = false;
+
+ $scope.internal = function($event) {
+
+ $scope.currentEditLink = null;
+
+ $scope.contentPickerOverlay = {};
+ $scope.contentPickerOverlay.view = "contentpicker";
+ $scope.contentPickerOverlay.multiPicker = false;
+ $scope.contentPickerOverlay.show = true;
+
+ $scope.contentPickerOverlay.submit = function(model) {
+
+ select(model.selection[0]);
+
+ $scope.contentPickerOverlay.show = false;
+ $scope.contentPickerOverlay = null;
+ };
+
+ $scope.contentPickerOverlay.close = function(oldModel) {
+ $scope.contentPickerOverlay.show = false;
+ $scope.contentPickerOverlay = null;
+ };
+
+ $event.preventDefault();
+ };
+
+ $scope.selectInternal = function($event, link) {
+
+ $scope.currentEditLink = link;
+
+ $scope.contentPickerOverlay = {};
+ $scope.contentPickerOverlay.view = "contentpicker";
+ $scope.contentPickerOverlay.multiPicker = false;
+ $scope.contentPickerOverlay.show = true;
+
+ $scope.contentPickerOverlay.submit = function(model) {
+
+ select(model.selection[0]);
+
+ $scope.contentPickerOverlay.show = false;
+ $scope.contentPickerOverlay = null;
+ };
+
+ $scope.contentPickerOverlay.close = function(oldModel) {
+ $scope.contentPickerOverlay.show = false;
+ $scope.contentPickerOverlay = null;
+ };
+
+ $event.preventDefault();
+
+ };
+
+ $scope.edit = function (idx) {
+ for (var i = 0; i < $scope.model.value.length; i++) {
+ $scope.model.value[i].edit = false;
+ }
+ $scope.model.value[idx].edit = true;
+ };
+
+ $scope.saveEdit = function (idx) {
+ $scope.model.value[idx].title = $scope.model.value[idx].caption;
+ $scope.model.value[idx].edit = false;
+ };
+
+ $scope.delete = function (idx) {
+ $scope.model.value.splice(idx, 1);
+ };
+
+ $scope.add = function ($event) {
+ if ($scope.newCaption == "") {
+ $scope.hasError = true;
+ } else {
+ if ($scope.addExternal) {
+ var newExtLink = new function() {
+ this.caption = $scope.newCaption;
+ this.link = $scope.newLink;
+ this.newWindow = $scope.newNewWindow;
+ this.edit = false;
+ this.isInternal = false;
+ this.type = "external";
+ this.title = $scope.newCaption;
+ };
+ $scope.model.value.push(newExtLink);
+ } else {
+ var newIntLink = new function() {
+ this.caption = $scope.newCaption;
+ this.link = $scope.newInternal;
+ this.newWindow = $scope.newNewWindow;
+ this.internal = $scope.newInternal;
+ this.edit = false;
+ this.isInternal = true;
+ this.internalName = $scope.newInternalName;
+ this.internalIcon = $scope.newInternalIcon;
+ this.type = "internal";
+ this.title = $scope.newCaption;
+ };
+ $scope.model.value.push(newIntLink);
+ }
+ $scope.newCaption = '';
+ $scope.newLink = 'http://';
+ $scope.newNewWindow = false;
+ $scope.newInternal = null;
+ $scope.newInternalName = '';
+ $scope.newInternalIcon = null;
+ }
+ $event.preventDefault();
+ };
+
+ $scope.switch = function ($event) {
+ $scope.addExternal = !$scope.addExternal;
+ $event.preventDefault();
+ };
+
+ $scope.switchLinkType = function ($event, link) {
+ link.isInternal = !link.isInternal;
+ link.type = link.isInternal ? "internal" : "external";
+ if (!link.isInternal)
+ link.link = $scope.newLink;
+ $event.preventDefault();
+ };
+
+ $scope.move = function (index, direction) {
+ var temp = $scope.model.value[index];
+ $scope.model.value[index] = $scope.model.value[index + direction];
+ $scope.model.value[index + direction] = temp;
+ };
+
+ //helper for determining if a user can add items
+ $scope.canAdd = function () {
+ return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible();
+ }
+
+ //helper that returns if an item can be sorted
+ $scope.canSort = function () {
+ return countVisible() > 1;
+ }
+
+ $scope.sortableOptions = {
+ axis: 'y',
+ handle: '.handle',
+ cursor: 'move',
+ cancel: '.no-drag',
+ containment: 'parent',
+ placeholder: 'sortable-placeholder',
+ forcePlaceholderSize: true,
+ helper: function (e, ui) {
+ // When sorting table rows, the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
+ ui.children().each(function () {
+ $(this).width($(this).width());
+ });
+ return ui;
+ },
+ items: '> tr:not(.unsortable)',
+ tolerance: 'pointer',
+ update: function (e, ui) {
+ // Get the new and old index for the moved element (using the URL as the identifier)
+ var newIndex = ui.item.index();
+ var movedLinkUrl = ui.item.attr('data-link');
+ var originalIndex = getElementIndexByUrl(movedLinkUrl);
+
+ // Move the element in the model
+ var movedElement = $scope.model.value[originalIndex];
+ $scope.model.value.splice(originalIndex, 1);
+ $scope.model.value.splice(newIndex, 0, movedElement);
+ },
+ start: function (e, ui) {
+ //ui.placeholder.html("
");
+
+ // Build a placeholder cell that spans all the cells in the row: http://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
+ var cellCount = 0;
+ $('td, th', ui.helper).each(function () {
+ // For each td or th try and get it's colspan attribute, and add that or 1 to the total
+ var colspan = 1;
+ var colspanAttr = $(this).attr('colspan');
+ if (colspanAttr > 1) {
+ colspan = colspanAttr;
+ }
+ cellCount += colspan;
+ });
+
+ // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
+ ui.placeholder.html('
').height(ui.item.height());
+ }
+ };
+
+ //helper to count what is visible
+ function countVisible() {
+ return $scope.model.value.length;
+ }
+
+ function isNumeric(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ }
+
+ function getElementIndexByUrl(url) {
+ for (var i = 0; i < $scope.model.value.length; i++) {
+ if ($scope.model.value[i].link == url) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ function select(data) {
+ if ($scope.currentEditLink != null) {
+ $scope.currentEditLink.internal = data.id;
+ $scope.currentEditLink.internalName = data.name;
+ $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon);
+ $scope.currentEditLink.link = data.id;
+ } else {
+ $scope.newInternal = data.id;
+ $scope.newInternalName = data.name;
+ $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon);
+ }
+ }
+ });
+
+angular.module("umbraco")
+ .controller("Umbraco.PropertyEditors.RTEController",
+ function ($rootScope, $scope, $q, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService) {
+
+ $scope.isLoading = true;
+
+ //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
+ // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because
+ // we have this mini content editor panel that can be launched with MNTP.
+ var d = new Date();
+ var n = d.getTime();
+ $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte";
+
+ var alreadyDirty = false;
+ function syncContent(editor){
+ editor.save();
+ angularHelper.safeApply($scope, function () {
+ $scope.model.value = editor.getContent();
+ });
+
+ if (!alreadyDirty) {
+ //make the form dirty manually so that the track changes works, setting our model doesn't trigger
+ // the angular bits because tinymce replaces the textarea.
+ var currForm = angularHelper.getCurrentForm($scope);
+ currForm.$setDirty();
+ alreadyDirty = true;
+ }
+ }
+
+ tinyMceService.configuration().then(function (tinyMceConfig) {
+
+ //config value from general tinymce.config file
+ var validElements = tinyMceConfig.validElements;
+
+ //These are absolutely required in order for the macros to render inline
+ //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
+ var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]";
+
+ var invalidElements = tinyMceConfig.inValidElements;
+ var plugins = _.map(tinyMceConfig.plugins, function (plugin) {
+ if (plugin.useOnFrontend) {
+ return plugin.name;
+ }
+ }).join(" ");
+
+ var editorConfig = $scope.model.config.editor;
+ if (!editorConfig || angular.isString(editorConfig)) {
+ editorConfig = tinyMceService.defaultPrevalues();
+ }
+
+ //config value on the data type
+ var toolbar = editorConfig.toolbar.join(" | ");
+ var stylesheets = [];
+ var styleFormats = [];
+ var await = [];
+ if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) {
+ editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
+ }
+
+ //queue file loading
+ if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded
+ await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope));
+ }
+
+ //queue rules loading
+ angular.forEach(editorConfig.stylesheets, function (val, key) {
+ stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime());
+ await.push(stylesheetResource.getRulesByName(val).then(function (rules) {
+ angular.forEach(rules, function (rule) {
+ var r = {};
+ r.title = rule.name;
+ if (rule.selector[0] == ".") {
+ r.inline = "span";
+ r.classes = rule.selector.substring(1);
+ }
+ else if (rule.selector[0] == "#") {
+ r.inline = "span";
+ r.attributes = { id: rule.selector.substring(1) };
+ }
+ else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) {
+ var split = rule.selector.split(".");
+ r.block = split[0];
+ r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " ");
+ }
+ else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) {
+ var split = rule.selector.split("#");
+ r.block = split[0];
+ r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1);
+ }
+ else {
+ r.block = rule.selector;
+ }
+
+ styleFormats.push(r);
+ });
+ }));
+ });
+
+
+ //stores a reference to the editor
+ var tinyMceEditor = null;
+
+ //wait for queue to end
+ $q.all(await).then(function () {
+
+ //create a baseline Config to exten upon
+ var baseLineConfigObj = {
+ mode: "exact",
+ skin: "umbraco",
+ plugins: plugins,
+ valid_elements: validElements,
+ invalid_elements: invalidElements,
+ extended_valid_elements: extendedValidElements,
+ menubar: false,
+ statusbar: false,
+ height: editorConfig.dimensions.height,
+ width: editorConfig.dimensions.width,
+ maxImageSize: editorConfig.maxImageSize,
+ toolbar: toolbar,
+ content_css: stylesheets,
+ relative_urls: false,
+ style_formats: styleFormats
+ };
+
+
+ if (tinyMceConfig.customConfig) {
+
+ //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to
+ // convert it to json instead of having it as a string since this is what tinymce requires
+ for (var i in tinyMceConfig.customConfig) {
+ var val = tinyMceConfig.customConfig[i];
+ if (val) {
+ val = val.toString().trim();
+ if (val.detectIsJson()) {
+ try {
+ tinyMceConfig.customConfig[i] = JSON.parse(val);
+ //now we need to check if this custom config key is defined in our baseline, if it is we don't want to
+ //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise
+ //if it's an object it will overwrite the baseline
+ if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) {
+ //concat it and below this concat'd array will overwrite the baseline in angular.extend
+ tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]);
+ }
+ }
+ catch (e) {
+ //cannot parse, we'll just leave it
+ }
+ }
+ }
+ }
+
+ angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
+ }
+
+ //set all the things that user configs should not be able to override
+ baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace!
+ baseLineConfigObj.setup = function (editor) {
+
+ //set the reference
+ tinyMceEditor = editor;
+
+ //enable browser based spell checking
+ editor.on('init', function (e) {
+ editor.getBody().setAttribute('spellcheck', true);
+ });
+
+ //We need to listen on multiple things here because of the nature of tinymce, it doesn't
+ //fire events when you think!
+ //The change event doesn't fire when content changes, only when cursor points are changed and undo points
+ //are created. the blur event doesn't fire if you insert content into the editor with a button and then
+ //press save.
+ //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can
+ //listen to both change and blur and also on our own 'saving' event. I think this will be best because a
+ //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
+ //save before the timeout elapsed.
+
+ //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce
+ // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers
+ // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed.
+ // see: http://issues.umbraco.org/issue/U4-4485
+ //var alreadyDirty = false;
+ //editor.on('change', function (e) {
+ // angularHelper.safeApply($scope, function () {
+ // $scope.model.value = editor.getContent();
+
+ // if (!alreadyDirty) {
+ // //make the form dirty manually so that the track changes works, setting our model doesn't trigger
+ // // the angular bits because tinymce replaces the textarea.
+ // var currForm = angularHelper.getCurrentForm($scope);
+ // currForm.$setDirty();
+ // alreadyDirty = true;
+ // }
+
+ // });
+ //});
+
+ //when we leave the editor (maybe)
+ editor.on('blur', function (e) {
+ editor.save();
+ angularHelper.safeApply($scope, function () {
+ $scope.model.value = editor.getContent();
+ });
+ });
+
+ //when buttons modify content
+ editor.on('ExecCommand', function (e) {
+ syncContent(editor);
+ });
+
+ // Update model on keypress
+ editor.on('KeyUp', function (e) {
+ syncContent(editor);
+ });
+
+ // Update model on change, i.e. copy/pasted text, plugins altering content
+ editor.on('SetContent', function (e) {
+ if (!e.initial) {
+ syncContent(editor);
+ }
+ });
+
+
+ editor.on('ObjectResized', function (e) {
+ var qs = "?width=" + e.width + "&height=" + e.height;
+ var srcAttr = $(e.target).attr("src");
+ var path = srcAttr.split("?")[0];
+ $(e.target).attr("data-mce-src", path + qs);
+
+ syncContent(editor);
+ });
+
+ tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) {
+ $scope.linkPickerOverlay = {
+ view: "linkpicker",
+ currentTarget: currentTarget,
+ show: true,
+ submit: function(model) {
+ tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
+ $scope.linkPickerOverlay.show = false;
+ $scope.linkPickerOverlay = null;
+ }
+ };
+ });
+
+ //Create the insert media plugin
+ tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){
+
+ $scope.mediaPickerOverlay = {
+ currentTarget: currentTarget,
+ onlyImages: true,
+ showDetails: true,
+ disableFolderSelect: true,
+ startNodeId: userData.startMediaId,
+ view: "mediapicker",
+ show: true,
+ submit: function(model) {
+ tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]);
+ $scope.mediaPickerOverlay.show = false;
+ $scope.mediaPickerOverlay = null;
+ }
+ };
+
+ });
+
+ //Create the embedded plugin
+ tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() {
+
+ $scope.embedOverlay = {
+ view: "embed",
+ show: true,
+ submit: function(model) {
+ tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview);
+ $scope.embedOverlay.show = false;
+ $scope.embedOverlay = null;
+ }
+ };
+
+ });
+
+
+ //Create the insert macro plugin
+ tinyMceService.createInsertMacro(editor, $scope, function(dialogData) {
+
+ $scope.macroPickerOverlay = {
+ view: "macropicker",
+ dialogData: dialogData,
+ show: true,
+ submit: function(model) {
+ var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
+ tinyMceService.insertMacroInEditor(editor, macroObject, $scope);
+ $scope.macroPickerOverlay.show = false;
+ $scope.macroPickerOverlay = null;
+ }
+ };
+
+ });
+ };
+
+
+
+
+ /** Loads in the editor */
+ function loadTinyMce() {
+
+ //we need to add a timeout here, to force a redraw so TinyMCE can find
+ //the elements needed
+ $timeout(function () {
+ tinymce.DOM.events.domLoaded = true;
+ tinymce.init(baseLineConfigObj);
+
+ $scope.isLoading = false;
+
+ }, 200, false);
+ }
+
+
+
+
+ loadTinyMce();
+
+ //here we declare a special method which will be called whenever the value has changed from the server
+ //this is instead of doing a watch on the model.value = faster
+ $scope.model.onValueChanged = function (newVal, oldVal) {
+ //update the display val again if it has changed from the server;
+ tinyMceEditor.setContent(newVal, { format: 'raw' });
+ //we need to manually fire this event since it is only ever fired based on loading from the DOM, this
+ // is required for our plugins listening to this event to execute
+ tinyMceEditor.fire('LoadContent', null);
+ };
+
+ //listen for formSubmitting event (the result is callback used to remove the event subscription)
+ var unsubscribe = $scope.$on("formSubmitting", function () {
+ //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
+ // we do parse it out on the server side but would be nice to do that on the client side before as well.
+ $scope.model.value = tinyMceEditor.getContent();
+ });
+
+ //when the element is disposed we need to unsubscribe!
+ // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
+ // element might still be there even after the modal has been hidden.
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+ });
+ });
+
+ });
+
+angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
+ function ($scope, $timeout, $log, tinyMceService, stylesheetResource, assetsService) {
+ var cfg = tinyMceService.defaultPrevalues();
+
+ if($scope.model.value){
+ if(angular.isString($scope.model.value)){
+ $scope.model.value = cfg;
+ }
+ }else{
+ $scope.model.value = cfg;
+ }
+
+ if (!$scope.model.value.stylesheets) {
+ $scope.model.value.stylesheets = [];
+ }
+ if (!$scope.model.value.toolbar) {
+ $scope.model.value.toolbar = [];
+ }
+ if (!$scope.model.value.maxImageSize && $scope.model.value.maxImageSize != 0) {
+ $scope.model.value.maxImageSize = cfg.maxImageSize;
+ }
+
+ tinyMceService.configuration().then(function(config){
+ $scope.tinyMceConfig = config;
+
+ // extend commands with properties for font-icon and if it is a custom command
+ $scope.tinyMceConfig.commands = _.map($scope.tinyMceConfig.commands, function (obj) {
+ var icon = getFontIcon(obj.frontEndCommand);
+ return angular.extend(obj, {
+ fontIcon: icon.name,
+ isCustom: icon.isCustom
+ });
+ });
+ });
+
+ stylesheetResource.getAll().then(function(stylesheets){
+ $scope.stylesheets = stylesheets;
+ });
+
+ $scope.selected = function(cmd, alias, lookup){
+ if (lookup && angular.isArray(lookup)) {
+ cmd.selected = lookup.indexOf(alias) >= 0;
+ return cmd.selected;
+ }
+ return false;
+ };
+
+ $scope.selectCommand = function(command){
+ var index = $scope.model.value.toolbar.indexOf(command.frontEndCommand);
+
+ if(command.selected && index === -1){
+ $scope.model.value.toolbar.push(command.frontEndCommand);
+ }else if(index >= 0){
+ $scope.model.value.toolbar.splice(index, 1);
+ }
+ };
+
+ $scope.selectStylesheet = function (css) {
+
+ var index = $scope.model.value.stylesheets.indexOf(css.name);
+
+ if(css.selected && index === -1){
+ $scope.model.value.stylesheets.push(css.name);
+ }else if(index >= 0){
+ $scope.model.value.stylesheets.splice(index, 1);
+ }
+ };
+
+ // map properties for specific commands
+ function getFontIcon(alias) {
+ var icon = { name: alias, isCustom: false };
+
+ switch (alias) {
+ case "codemirror":
+ icon.name = "code";
+ icon.isCustom = false;
+ break;
+ case "styleselect":
+ icon.name = "icon-list";
+ icon.isCustom = true;
+ break;
+ case "umbembeddialog":
+ icon.name = "icon-tv";
+ icon.isCustom = true;
+ break;
+ case "umbmediapicker":
+ icon.name = "icon-picture";
+ icon.isCustom = true;
+ break;
+ case "umbmacro":
+ icon.name = "icon-settings-alt";
+ icon.isCustom = true;
+ break;
+ case "umbmacro":
+ icon.name = "icon-settings-alt";
+ icon.isCustom = true;
+ break;
+ default:
+ icon.name = alias;
+ icon.isCustom = false;
+ }
+
+ return icon;
+ }
+
+ var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
+
+ var commands = _.where($scope.tinyMceConfig.commands, {selected: true});
+ $scope.model.value.toolbar = _.pluck(commands, "frontEndCommand");
+
+ });
+
+ // when the scope is destroyed we need to unsubscribe
+ $scope.$on('$destroy', function () {
+ unsubscribe();
+ });
+
+ // load TinyMCE skin which contains css for font-icons
+ assetsService.loadCss("lib/tinymce/skins/umbraco/skin.min.css");
+ });
+function sliderController($scope, $log, $element, assetsService, angularHelper) {
+
+ //configure some defaults
+ if (!$scope.model.config.orientation) {
+ $scope.model.config.orientation = "horizontal";
+ }
+ if (!$scope.model.config.enableRange) {
+ $scope.model.config.enableRange = false;
+ }
+ else {
+ $scope.model.config.enableRange = $scope.model.config.enableRange === "1" ? true : false;
+ }
+
+ if (!$scope.model.config.initVal1) {
+ $scope.model.config.initVal1 = 0;
+ }
+ else {
+ $scope.model.config.initVal1 = parseFloat($scope.model.config.initVal1);
+ }
+ if (!$scope.model.config.initVal2) {
+ $scope.model.config.initVal2 = 0;
+ }
+ else {
+ $scope.model.config.initVal2 = parseFloat($scope.model.config.initVal2);
+ }
+ if (!$scope.model.config.minVal) {
+ $scope.model.config.minVal = 0;
+ }
+ else {
+ $scope.model.config.minVal = parseFloat($scope.model.config.minVal);
+ }
+ if (!$scope.model.config.maxVal) {
+ $scope.model.config.maxVal = 100;
+ }
+ else {
+ $scope.model.config.maxVal = parseFloat($scope.model.config.maxVal);
+ }
+ if (!$scope.model.config.step) {
+ $scope.model.config.step = 1;
+ }
+ else {
+ $scope.model.config.step = parseFloat($scope.model.config.step);
+ }
+
+ if (!$scope.model.config.handle) {
+ $scope.model.config.handle = "round";
+ }
+
+ if (!$scope.model.config.reversed) {
+ $scope.model.config.reversed = false;
+ }
+ else {
+ $scope.model.config.reversed = $scope.model.config.reversed === "1" ? true : false;
+ }
+
+ if (!$scope.model.config.tooltip) {
+ $scope.model.config.tooltip = "show";
+ }
+
+ if (!$scope.model.config.tooltipSplit) {
+ $scope.model.config.tooltipSplit = false;
+ }
+ else {
+ $scope.model.config.tooltipSplit = $scope.model.config.tooltipSplit === "1" ? true : false;
+ }
+
+ if ($scope.model.config.tooltipFormat) {
+ $scope.model.config.formatter = function (value) {
+ if (angular.isArray(value) && $scope.model.config.enableRange) {
+ return $scope.model.config.tooltipFormat.replace("{0}", value[0]).replace("{1}", value[1]);
+ } else {
+ return $scope.model.config.tooltipFormat.replace("{0}", value);
+ }
+ }
+ }
+
+ if (!$scope.model.config.ticks) {
+ $scope.model.config.ticks = [];
+ }
+ else {
+ // returns comma-separated string to an array, e.g. [0, 100, 200, 300, 400]
+ $scope.model.config.ticks = _.map($scope.model.config.ticks.split(','), function (item) {
+ return parseInt(item.trim());
+ });
+ }
+
+ if (!$scope.model.config.ticksPositions) {
+ $scope.model.config.ticksPositions = [];
+ }
+ else {
+ // returns comma-separated string to an array, e.g. [0, 30, 60, 70, 90, 100]
+ $scope.model.config.ticksPositions = _.map($scope.model.config.ticksPositions.split(','), function (item) {
+ return parseInt(item.trim());
+ });
+ console.log($scope.model.config.ticksPositions);
+ }
+
+ if (!$scope.model.config.ticksLabels) {
+ $scope.model.config.ticksLabels = [];
+ }
+ else {
+ // returns comma-separated string to an array, e.g. ['$0', '$100', '$200', '$300', '$400']
+ $scope.model.config.ticksLabels = _.map($scope.model.config.ticksLabels.split(','), function (item) {
+ return item.trim();
+ });
+ }
+
+ if (!$scope.model.config.ticksSnapBounds) {
+ $scope.model.config.ticksSnapBounds = 0;
+ }
+ else {
+ $scope.model.config.ticksSnapBounds = parseFloat($scope.model.config.ticksSnapBounds);
+ }
+
+ /** This creates the slider with the model values - it's called on startup and if the model value changes */
+ function createSlider() {
+
+ //the value that we'll give the slider - if it's a range, we store our value as a comma separated val but this slider expects an array
+ var sliderVal = null;
+
+ //configure the model value based on if range is enabled or not
+ if ($scope.model.config.enableRange == true) {
+ //If no value saved yet - then use default value
+ //If it contains a single value - then also create a new array value
+ if (!$scope.model.value || $scope.model.value.indexOf(",") == -1) {
+ var i1 = parseFloat($scope.model.config.initVal1);
+ var i2 = parseFloat($scope.model.config.initVal2);
+ sliderVal = [
+ isNaN(i1) ? $scope.model.config.minVal : (i1 >= $scope.model.config.minVal ? i1 : $scope.model.config.minVal),
+ isNaN(i2) ? $scope.model.config.maxVal : (i2 > i1 ? (i2 <= $scope.model.config.maxVal ? i2 : $scope.model.config.maxVal) : $scope.model.config.maxVal)
+ ];
+ }
+ else {
+ //this will mean it's a delimited value stored in the db, convert it to an array
+ sliderVal = _.map($scope.model.value.split(','), function (item) {
+ return parseFloat(item);
+ });
+ }
+ }
+ else {
+ //If no value saved yet - then use default value
+ if ($scope.model.value) {
+ sliderVal = parseFloat($scope.model.value);
+ }
+ else {
+ sliderVal = $scope.model.config.initVal1;
+ }
+ }
+
+ // Initialise model value if not set
+ if (!$scope.model.value) {
+ setModelValueFromSlider(sliderVal);
+ }
+
+ //initiate slider, add event handler and get the instance reference (stored in data)
+ var slider = $element.find('.slider-item').bootstrapSlider({
+ max: $scope.model.config.maxVal,
+ min: $scope.model.config.minVal,
+ orientation: $scope.model.config.orientation,
+ selection: $scope.model.config.reversed ? "after" : "before",
+ step: $scope.model.config.step,
+ precision: $scope.model.config.precision,
+ tooltip: $scope.model.config.tooltip,
+ tooltip_split: $scope.model.config.tooltipSplit,
+ tooltip_position: $scope.model.config.tooltipPosition,
+ handle: $scope.model.config.handle,
+ reversed: $scope.model.config.reversed,
+ ticks: $scope.model.config.ticks,
+ ticks_positions: $scope.model.config.ticksPositions,
+ ticks_labels: $scope.model.config.ticksLabels,
+ ticks_snap_bounds: $scope.model.config.ticksSnapBounds,
+ formatter: $scope.model.config.formatter,
+ range: $scope.model.config.enableRange,
+ //set the slider val - we cannot do this with data- attributes when using ranges
+ value: sliderVal
+ }).on('slideStop', function (e) {
+ var value = e.value;
+ angularHelper.safeApply($scope, function () {
+ setModelValueFromSlider(value);
+ });
+ }).data('slider');
+ }
+
+ /** Called on start-up when no model value has been applied and on change of the slider via the UI - updates
+ the model with the currently selected slider value(s) **/
+ function setModelValueFromSlider(sliderVal) {
+ //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value
+ if ($scope.model.config.enableRange == true) {
+ $scope.model.value = sliderVal.join(",");
+ }
+ else {
+ $scope.model.value = sliderVal.toString();
+ }
+ }
+
+ //tell the assetsService to load the bootstrap slider
+ //libs from the plugin folder
+ assetsService
+ .loadJs("lib/slider/js/bootstrap-slider.js")
+ .then(function () {
+
+ createSlider();
+
+ //here we declare a special method which will be called whenever the value has changed from the server
+ //this is instead of doing a watch on the model.value = faster
+ $scope.model.onValueChanged = function (newVal, oldVal) {
+ if (newVal != oldVal) {
+ createSlider();
+ }
+ };
+
+ });
+
+ //load the separate css for the editor to avoid it blocking our js loading
+ assetsService.loadCss("lib/slider/bootstrap-slider.css");
+ assetsService.loadCss("lib/slider/bootstrap-slider-custom.css");
+}
+angular.module("umbraco").controller("Umbraco.PropertyEditors.SliderController", sliderController);
+angular.module("umbraco")
+.controller("Umbraco.PropertyEditors.TagsController",
+ function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) {
+
+ var $typeahead;
+
+ $scope.isLoading = true;
+ $scope.tagToAdd = "";
+
+ assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js").then(function () {
+
+ $scope.isLoading = false;
+
+ //load current value
+
+ if ($scope.model.value) {
+ if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
+ //it is csv
+ if (!$scope.model.value) {
+ $scope.model.value = [];
+ }
+ else {
+ if($scope.model.value.length > 0) {
+ $scope.model.value = $scope.model.value.split(",");
+ }
+ }
+ }
+ }
+ else {
+ $scope.model.value = [];
+ }
+
+ // Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected)
+ $scope.validateMandatory = function () {
+ return {
+ isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value.length > 0),
+ errorMsg: "Value cannot be empty",
+ errorKey: "required"
+ };
+ }
+
+ //Helper method to add a tag on enter or on typeahead select
+ function addTag(tagToAdd) {
+ if (tagToAdd != null && tagToAdd.length > 0) {
+ if ($scope.model.value.indexOf(tagToAdd) < 0) {
+ $scope.model.value.push(tagToAdd);
+ //this is required to re-validate
+ $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
+ }
+ }
+ }
+
+ $scope.addTagOnEnter = function (e) {
+ var code = e.keyCode || e.which;
+ if (code == 13) { //Enter keycode
+ if ($element.find('.tags-' + $scope.model.alias).parent().find(".tt-dropdown-menu .tt-cursor").length === 0) {
+ //this is required, otherwise the html form will attempt to submit.
+ e.preventDefault();
+ $scope.addTag();
+ }
+ }
+ };
+
+ $scope.addTag = function () {
+ //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down
+ //we need to use jquery because typeahead duplicates the text box
+ addTag($scope.tagToAdd);
+ $scope.tagToAdd = "";
+ //this clears the value stored in typeahead so it doesn't try to add the text again
+ // http://issues.umbraco.org/issue/U4-4947
+ $typeahead.typeahead('val', '');
+ };
+
+
+
+ $scope.removeTag = function (tag) {
+ var i = $scope.model.value.indexOf(tag);
+ if (i >= 0) {
+ $scope.model.value.splice(i, 1);
+ //this is required to re-validate
+ $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
+ }
+ };
+
+ //vice versa
+ $scope.model.onValueChanged = function (newVal, oldVal) {
+ //update the display val again if it has changed from the server
+ $scope.model.value = newVal;
+
+ if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
+ //it is csv
+ if (!$scope.model.value) {
+ $scope.model.value = [];
+ }
+ else {
+ $scope.model.value = $scope.model.value.split(",");
+ }
+ }
+ };
+
+ //configure the tags data source
+
+ //helper method to format the data for bloodhound
+ function dataTransform(list) {
+ //transform the result to what bloodhound wants
+ var tagList = _.map(list, function (i) {
+ return { value: i.text };
+ });
+ // remove current tags from the list
+ return $.grep(tagList, function (tag) {
+ return ($.inArray(tag.value, $scope.model.value) === -1);
+ });
+ }
+
+ // helper method to remove current tags
+ function removeCurrentTagsFromSuggestions(suggestions) {
+ return $.grep(suggestions, function (suggestion) {
+ return ($.inArray(suggestion.value, $scope.model.value) === -1);
+ });
+ }
+
+ var tagsHound = new Bloodhound({
+ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ dupDetector : function(remoteMatch, localMatch) {
+ return (remoteMatch["value"] == localMatch["value"]);
+ },
+ //pre-fetch the tags for this category
+ prefetch: {
+ url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]),
+ //TTL = 5 minutes
+ ttl: 300000,
+ filter: dataTransform
+ },
+ //dynamically get the tags for this category (they may have changed on the server)
+ remote: {
+ url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]),
+ filter: dataTransform
+ }
+ });
+
+ tagsHound.initialize(true);
+
+ //configure the type ahead
+ $timeout(function () {
+
+ $typeahead = $element.find('.tags-' + $scope.model.alias).typeahead(
+ {
+ //This causes some strangeness as it duplicates the textbox, best leave off for now.
+ hint: false,
+ highlight: true,
+ cacheKey: new Date(), // Force a cache refresh each time the control is initialized
+ minLength: 1
+ }, {
+ //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options
+ // name = the data set name, we'll make this the tag group name
+ name: $scope.model.config.group,
+ displayKey: "value",
+ source: function (query, cb) {
+ tagsHound.get(query, function (suggestions) {
+ cb(removeCurrentTagsFromSuggestions(suggestions));
+ });
+ },
+ }).bind("typeahead:selected", function (obj, datum, name) {
+ angularHelper.safeApply($scope, function () {
+ addTag(datum["value"]);
+ $scope.tagToAdd = "";
+ // clear the typed text
+ $typeahead.typeahead('val', '');
+ });
+
+ }).bind("typeahead:autocompleted", function (obj, datum, name) {
+ angularHelper.safeApply($scope, function () {
+ addTag(datum["value"]);
+ $scope.tagToAdd = "";
+ });
+
+ }).bind("typeahead:opened", function (obj) {
+ //console.log("opened ");
+ });
+ });
+
+ $scope.$on('$destroy', function () {
+ tagsHound.clearPrefetchCache();
+ tagsHound.clearRemoteCache();
+ $element.find('.tags-' + $scope.model.alias).typeahead('destroy');
+ delete tagsHound;
+ });
+
+ });
+
+ }
+);
+//this controller simply tells the dialogs service to open a mediaPicker window
+//with a specified callback, this callback will receive an object with a selection on it
+angular.module('umbraco').controller("Umbraco.PropertyEditors.EmbeddedContentController",
+ function($rootScope, $scope, $log){
+
+ $scope.showForm = false;
+ $scope.fakeData = [];
+
+ $scope.create = function(){
+ $scope.showForm = true;
+ $scope.fakeData = angular.copy($scope.model.config.fields);
+ };
+
+ $scope.show = function(){
+ $scope.showCode = true;
+ };
+
+ $scope.add = function(){
+ $scope.showForm = false;
+ if ( !($scope.model.value instanceof Array)) {
+ $scope.model.value = [];
+ }
+
+ $scope.model.value.push(angular.copy($scope.fakeData));
+ $scope.fakeData = [];
+ };
+});
+angular.module('umbraco').controller("Umbraco.PropertyEditors.UrlListController",
+ function($rootScope, $scope, $filter) {
+
+ function formatDisplayValue() {
+ if (angular.isArray($scope.model.value)) {
+ //it's the json value
+ $scope.renderModel = _.map($scope.model.value, function (item) {
+ return {
+ url: item.url,
+ linkText: item.linkText,
+ urlTarget: (item.target) ? item.target : "_blank",
+ icon: (item.icon) ? item.icon : "icon-out"
+ };
+ });
+ }
+ else {
+ //it's the default csv value
+ $scope.renderModel = _.map($scope.model.value.split(","), function (item) {
+ return {
+ url: item,
+ linkText: "",
+ urlTarget: ($scope.config && $scope.config.target) ? $scope.config.target : "_blank",
+ icon: ($scope.config && $scope.config.icon) ? $scope.config.icon : "icon-out"
+ };
+ });
+ }
+ }
+
+ $scope.getUrl = function(valueUrl) {
+ if (valueUrl.indexOf("/") >= 0) {
+ return valueUrl;
+ }
+ return "#";
+ };
+
+ formatDisplayValue();
+
+ //here we declare a special method which will be called whenever the value has changed from the server
+ //this is instead of doing a watch on the model.value = faster
+ $scope.model.onValueChanged = function(newVal, oldVal) {
+ //update the display val again
+ formatDisplayValue();
+ };
+
+ });
+
+})();
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbraco.directives.js b/WebCms/Umbraco/Js/umbraco.directives.js
new file mode 100644
index 0000000..8d2c1cd
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.directives.js
@@ -0,0 +1,11201 @@
+/*! umbraco
+ * https://github.com/umbraco/umbraco-cms/
+ * Copyright (c) 2016 Umbraco HQ;
+ * Licensed
+ */
+
+(function() {
+
+angular.module("umbraco.directives", ["umbraco.directives.editors", "umbraco.directives.html", "umbraco.directives.validation", "ui.sortable"]);
+angular.module("umbraco.directives.editors", []);
+angular.module("umbraco.directives.html", []);
+angular.module("umbraco.directives.validation", []);
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:autoScale
+* @element div
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+* @function
+* @description
+* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
+* So if you only want to scale the div to 70 pixels from the bottom you pass "70"
+
+* @example
+*
+*
+*
+*
+*
+**/
+
+angular.module("umbraco.directives")
+ .directive('autoScale', function ($window) {
+ return function (scope, el, attrs) {
+
+ var totalOffset = 0;
+ var offsety = parseInt(attrs.autoScale, 10);
+ var window = angular.element($window);
+ if (offsety !== undefined){
+ totalOffset += offsety;
+ }
+
+ setTimeout(function () {
+ el.height(window.height() - (el.offset().top + totalOffset));
+ }, 500);
+
+ window.bind("resize", function () {
+ el.height(window.height() - (el.offset().top + totalOffset));
+ });
+
+ };
+ });
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:detectFold
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+* @description This is used for the editor buttons to ensure they are displayed correctly if the horizontal overflow of the editor
+* exceeds the height of the window
+**/
+
+angular.module("umbraco.directives.html")
+ .directive('detectFold', function ($timeout, $log, windowResizeListener) {
+ return {
+ require: "^?umbTabs",
+ restrict: 'A',
+ link: function (scope, el, attrs, tabsCtrl) {
+
+ var firstRun = false;
+ var parent = $(".umb-panel-body");
+ var winHeight = $(window).height();
+ var calculate = function () {
+ if (el && el.is(":visible") && !el.hasClass("umb-bottom-bar")) {
+
+ //now that the element is visible, set the flag in a couple of seconds,
+ // this will ensure that loading time of a current tab get's completed and that
+ // we eventually stop watching to save on CPU time
+ $timeout(function() {
+ firstRun = true;
+ }, 4000);
+
+ //var parent = el.parent();
+ var hasOverflow = parent.innerHeight() < parent[0].scrollHeight;
+ //var belowFold = (el.offset().top + el.height()) > winHeight;
+ if (hasOverflow) {
+ el.addClass("umb-bottom-bar");
+
+ //I wish we didn't have to put this logic here but unfortunately we
+ // do. This needs to calculate the left offest to place the bottom bar
+ // depending on if the left column splitter has been moved by the user
+ // (based on the nav-resize directive)
+ var wrapper = $("#mainwrapper");
+ var contentPanel = $("#leftcolumn").next();
+ var contentPanelLeftPx = contentPanel.css("left");
+
+ el.css({ left: contentPanelLeftPx });
+ }
+ }
+ return firstRun;
+ };
+
+ var resizeCallback = function(size) {
+ winHeight = size.height;
+ el.removeClass("umb-bottom-bar");
+ calculate();
+ };
+
+ windowResizeListener.register(resizeCallback);
+
+ //Only execute the watcher if this tab is the active (first) tab on load, otherwise there's no reason to execute
+ // the watcher since it will be recalculated when the tab changes!
+ if (el.closest(".umb-tab-pane").index() === 0) {
+ //run a watcher to ensure that the calculation occurs until it's firstRun but ensure
+ // the calculations are throttled to save a bit of CPU
+ var listener = scope.$watch(_.throttle(calculate, 1000), function (newVal, oldVal) {
+ if (newVal !== oldVal) {
+ listener();
+ }
+ });
+ }
+
+ //listen for tab changes
+ if (tabsCtrl != null) {
+ tabsCtrl.onTabShown(function (args) {
+ calculate();
+ });
+ }
+
+ //ensure to unregister
+ scope.$on('$destroy', function() {
+ windowResizeListener.unregister(resizeCallback);
+ });
+ }
+ };
+ });
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbItemSorter
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+* @function
+* @element ANY
+* @restrict E
+* @description A re-usable directive for sorting items
+**/
+
+function umbItemSorter(angularHelper) {
+ return {
+ scope: {
+ model: "="
+ },
+ restrict: "E", // restrict to an element
+ replace: true, // replace the html element with the template
+ templateUrl: 'views/directives/_obsolete/umb-item-sorter.html',
+ link: function(scope, element, attrs, ctrl) {
+ var defaultModel = {
+ okButton: "Ok",
+ successMsg: "Sorting successful",
+ complete: false
+ };
+ //assign user vals to default
+ angular.extend(defaultModel, scope.model);
+ //re-assign merged to user
+ scope.model = defaultModel;
+
+ scope.performSort = function() {
+ scope.$emit("umbItemSorter.sorting", {
+ sortedItems: scope.model.itemsToSort
+ });
+ };
+
+ scope.handleCancel = function () {
+ scope.$emit("umbItemSorter.cancel");
+ };
+
+ scope.handleOk = function() {
+ scope.$emit("umbItemSorter.ok");
+ };
+
+ //defines the options for the jquery sortable
+ scope.sortableOptions = {
+ axis: 'y',
+ cursor: "move",
+ placeholder: "ui-sortable-placeholder",
+ update: function (ev, ui) {
+ //highlight the item when the position is changed
+ $(ui.item).effect("highlight", { color: "#049cdb" }, 500);
+ },
+ stop: function (ev, ui) {
+ //the ui-sortable directive already ensures that our list is re-sorted, so now we just
+ // need to update the sortOrder to the index of each item
+ angularHelper.safeApply(scope, function () {
+ angular.forEach(scope.itemsToSort, function (val, index) {
+ val.sortOrder = index + 1;
+ });
+
+ });
+ }
+ };
+ }
+ };
+}
+
+angular.module('umbraco.directives').directive("umbItemSorter", umbItemSorter);
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbContentName
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+* @restrict E
+* @function
+* @description
+* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
+**/
+
+angular.module("umbraco.directives")
+ .directive('umbContentName', function ($timeout, localizationService) {
+ return {
+ require: "ngModel",
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/directives/_obsolete/umb-content-name.html',
+
+ scope: {
+ placeholder: '@placeholder',
+ model: '=ngModel',
+ ngDisabled: '='
+ },
+ link: function(scope, element, attrs, ngModel) {
+
+ var inputElement = element.find("input");
+ if(scope.placeholder && scope.placeholder[0] === "@"){
+ localizationService.localize(scope.placeholder.substring(1))
+ .then(function(value){
+ scope.placeholder = value;
+ });
+ }
+
+ var mX, mY, distance;
+
+ function calculateDistance(elem, mouseX, mouseY) {
+
+ var cx = Math.max(Math.min(mouseX, elem.offset().left + elem.width()), elem.offset().left);
+ var cy = Math.max(Math.min(mouseY, elem.offset().top + elem.height()), elem.offset().top);
+ return Math.sqrt((mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy));
+ }
+
+ var mouseMoveDebounce = _.throttle(function (e) {
+ mX = e.pageX;
+ mY = e.pageY;
+ // not focused and not over element
+ if (!inputElement.is(":focus") && !inputElement.hasClass("ng-invalid")) {
+ // on page
+ if (mX >= inputElement.offset().left) {
+ distance = calculateDistance(inputElement, mX, mY);
+ if (distance <= 155) {
+
+ distance = 1 - (100 / 150 * distance / 100);
+ inputElement.css("border", "1px solid rgba(175,175,175, " + distance + ")");
+ inputElement.css("background-color", "rgba(255,255,255, " + distance + ")");
+ }
+ }
+
+ }
+
+ }, 15);
+
+ $(document).bind("mousemove", mouseMoveDebounce);
+
+ $timeout(function(){
+ if(!scope.model){
+ scope.goEdit();
+ }
+ }, 100, false);
+
+ scope.goEdit = function(){
+ scope.editMode = true;
+
+ $timeout(function () {
+ inputElement.focus();
+ }, 100, false);
+ };
+
+ scope.exitEdit = function(){
+ if(scope.model && scope.model !== ""){
+ scope.editMode = false;
+ }
+ };
+
+ //unbind doc event!
+ scope.$on('$destroy', function () {
+ $(document).unbind("mousemove", mouseMoveDebounce);
+ });
+ }
+ };
+ });
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbHeader
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+* @restrict E
+* @function
+* @description
+* The header on an editor that contains tabs using bootstrap tabs - THIS IS OBSOLETE, use umbTabHeader instead
+**/
+
+angular.module("umbraco.directives")
+.directive('umbHeader', function ($parse, $timeout) {
+ return {
+ restrict: 'E',
+ replace: true,
+ transclude: 'true',
+ templateUrl: 'views/directives/_obsolete/umb-header.html',
+ //create a new isolated scope assigning a tabs property from the attribute 'tabs'
+ //which is bound to the parent scope property passed in
+ scope: {
+ tabs: "="
+ },
+ link: function (scope, iElement, iAttrs) {
+
+ scope.showTabs = iAttrs.tabs ? true : false;
+ scope.visibleTabs = [];
+
+ //since tabs are loaded async, we need to put a watch on them to determine
+ // when they are loaded, then we can close the watch
+ var tabWatch = scope.$watch("tabs", function (newValue, oldValue) {
+
+ angular.forEach(newValue, function(val, index){
+ var tab = {id: val.id, label: val.label};
+ scope.visibleTabs.push(tab);
+ });
+
+ //don't process if we cannot or have already done so
+ if (!newValue) {return;}
+ if (!newValue.length || newValue.length === 0){return;}
+
+ //we need to do a timeout here so that the current sync operation can complete
+ // and update the UI, then this will fire and the UI elements will be available.
+ $timeout(function () {
+
+ //use bootstrap tabs API to show the first one
+ iElement.find(".nav-tabs a:first").tab('show');
+
+ //enable the tab drop
+ iElement.find('.nav-pills, .nav-tabs').tabdrop();
+
+ //ensure to destroy tabdrop (unbinds window resize listeners)
+ scope.$on('$destroy', function () {
+ iElement.find('.nav-pills, .nav-tabs').tabdrop("destroy");
+ });
+
+ //stop watching now
+ tabWatch();
+ }, 200);
+
+ });
+ }
+ };
+});
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbLogin
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+* @function
+* @element ANY
+* @restrict E
+**/
+
+function loginDirective() {
+ return {
+ restrict: "E", // restrict to an element
+ replace: true, // replace the html element with the template
+ templateUrl: 'views/directives/_obsolete/umb-login.html'
+ };
+}
+
+angular.module('umbraco.directives').directive("umbLogin", loginDirective);
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbOptionsMenu
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+* @function
+* @element ANY
+* @restrict E
+**/
+
+angular.module("umbraco.directives")
+.directive('umbOptionsMenu', function ($injector, treeService, navigationService, umbModelMapper, appState) {
+ return {
+ scope: {
+ currentSection: "@",
+ currentNode: "="
+ },
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/directives/_obsolete/umb-optionsmenu.html',
+ link: function (scope, element, attrs, ctrl) {
+
+ //adds a handler to the context menu item click, we need to handle this differently
+ //depending on what the menu item is supposed to do.
+ scope.executeMenuItem = function (action) {
+ navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection);
+ };
+
+ //callback method to go and get the options async
+ scope.getOptions = function () {
+
+ if (!scope.currentNode) {
+ return;
+ }
+
+ //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu)
+ appState.setMenuState("currentNode", scope.currentNode);
+
+ if (!scope.actions) {
+ treeService.getMenu({ treeNode: scope.currentNode })
+ .then(function (data) {
+ scope.actions = data.menuItems;
+ });
+ }
+ };
+
+ }
+ };
+});
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbPhotoFolder
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+* @restrict E
+**/
+
+angular.module("umbraco.directives.html")
+ .directive('umbPhotoFolder', function($compile, $log, $timeout, $filter, umbPhotoFolderHelper) {
+
+ return {
+ restrict: 'E',
+ replace: true,
+ require: '?ngModel',
+ terminate: true,
+ templateUrl: 'views/directives/_obsolete/umb-photo-folder.html',
+ link: function(scope, element, attrs, ngModel) {
+
+ var lastWatch = null;
+
+ ngModel.$render = function() {
+ if (ngModel.$modelValue) {
+
+ $timeout(function() {
+ var photos = ngModel.$modelValue;
+
+ scope.clickHandler = scope.$eval(element.attr('on-click'));
+
+
+ var imagesOnly = element.attr('images-only') === "true";
+
+
+ var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5;
+ var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0;
+ var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420;
+ var minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 100;
+ var maxHeight = element.attr('max-height') ? parseInt(element.attr('max-height'), 10) : 300;
+ var idealImgPerRow = element.attr('ideal-items-per-row') ? parseInt(element.attr('ideal-items-per-row'), 10) : 5;
+ var fixedRowWidth = Math.max(element.width(), minWidth);
+
+ scope.containerStyle = { width: fixedRowWidth + "px" };
+ scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly);
+
+ if (attrs.filterBy) {
+
+ //we track the watches that we create, we don't want to create multiple, so clear it
+ // if it already exists before creating another.
+ if (lastWatch) {
+ lastWatch();
+ }
+
+ //TODO: Need to debounce this so it doesn't filter too often!
+ lastWatch = scope.$watch(attrs.filterBy, function (newVal, oldVal) {
+ if (newVal && newVal !== oldVal) {
+ var p = $filter('filter')(photos, newVal, false);
+ scope.baseline = 0;
+ var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly);
+ scope.rows = m;
+ }
+ });
+ }
+
+ }, 500); //end timeout
+ } //end if modelValue
+
+ }; //end $render
+ }
+ };
+ });
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbSort
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+*
+* @element div
+* @function
+*
+* @description
+* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
+* So if you only want to scale the div to 70 pixels from the bottom you pass "70"
+*
+* @example
+*
+*
+*
+*
+*
+**/
+
+angular.module("umbraco.directives")
+ .value('umbSortContextInternal',{})
+ .directive('umbSort', function($log,umbSortContextInternal) {
+ return {
+ require: '?ngModel',
+ link: function(scope, element, attrs, ngModel) {
+ var adjustment;
+
+ var cfg = scope.$eval(element.attr('umb-sort')) || {};
+
+ scope.model = ngModel;
+
+ scope.opts = cfg;
+ scope.opts.containerSelector= cfg.containerSelector || ".umb-" + cfg.group + "-container",
+ scope.opts.nested= cfg.nested || true,
+ scope.opts.drop= cfg.drop || true,
+ scope.opts.drag= cfg.drag || true,
+ scope.opts.clone = cfg.clone || "";
+ scope.opts.mode = cfg.mode || "list";
+
+ scope.opts.itemSelectorFull = $.trim(scope.opts.itemPath + " " + scope.opts.itemSelector);
+
+ /*
+ scope.opts.isValidTarget = function(item, container) {
+ if(container.el.is(".umb-" + scope.opts.group + "-container")){
+ return true;
+ }
+ return false;
+ };
+ */
+
+ element.addClass("umb-sort");
+ element.addClass("umb-" + cfg.group + "-container");
+
+ scope.opts.onDrag = function (item, position) {
+ if(scope.opts.mode === "list"){
+ item.css({
+ left: position.left - adjustment.left,
+ top: position.top - adjustment.top
+ });
+ }
+ };
+
+
+ scope.opts.onDrop = function (item, targetContainer, _super) {
+
+ if(scope.opts.mode === "list"){
+ //list mode
+ var clonedItem = $(scope.opts.clone).css({height: 0});
+ item.after(clonedItem);
+ clonedItem.animate({'height': item.height()});
+
+ item.animate(clonedItem.position(), function () {
+ clonedItem.detach();
+ _super(item);
+ });
+ }
+
+ var children = $(scope.opts.itemSelectorFull, targetContainer.el);
+ var targetIndex = children.index(item);
+ var targetScope = $(targetContainer.el[0]).scope();
+
+
+ if(targetScope === umbSortContextInternal.sourceScope){
+ if(umbSortContextInternal.sourceScope.opts.onSortHandler){
+ var _largs = {
+ oldIndex: umbSortContextInternal.sourceIndex,
+ newIndex: targetIndex,
+ scope: umbSortContextInternal.sourceScope
+ };
+
+ umbSortContextInternal.sourceScope.opts.onSortHandler.call(this, item, _largs);
+ }
+ }else{
+
+
+ if(targetScope.opts.onDropHandler){
+ var args = {
+ sourceScope: umbSortContextInternal.sourceScope,
+ sourceIndex: umbSortContextInternal.sourceIndex,
+ sourceContainer: umbSortContextInternal.sourceContainer,
+
+ targetScope: targetScope,
+ targetIndex: targetIndex,
+ targetContainer: targetContainer
+ };
+
+ targetScope.opts.onDropHandler.call(this, item, args);
+ }
+
+ if(umbSortContextInternal.sourceScope.opts.onReleaseHandler){
+ var _args = {
+ sourceScope: umbSortContextInternal.sourceScope,
+ sourceIndex: umbSortContextInternal.sourceIndex,
+ sourceContainer: umbSortContextInternal.sourceContainer,
+
+ targetScope: targetScope,
+ targetIndex: targetIndex,
+ targetContainer: targetContainer
+ };
+
+ umbSortContextInternal.sourceScope.opts.onReleaseHandler.call(this, item, _args);
+ }
+ }
+ };
+
+ scope.changeIndex = function(from, to){
+ scope.$apply(function(){
+ var i = ngModel.$modelValue.splice(from, 1)[0];
+ ngModel.$modelValue.splice(to, 0, i);
+ });
+ };
+
+ scope.move = function(args){
+ var from = args.sourceIndex;
+ var to = args.targetIndex;
+
+ if(args.sourceContainer === args.targetContainer){
+ scope.changeIndex(from, to);
+ }else{
+ scope.$apply(function(){
+ var i = args.sourceScope.model.$modelValue.splice(from, 1)[0];
+ args.targetScope.model.$modelvalue.splice(to,0, i);
+ });
+ }
+ };
+
+ scope.opts.onDragStart = function (item, container, _super) {
+ var children = $(scope.opts.itemSelectorFull, container.el);
+ var offset = item.offset();
+
+ umbSortContextInternal.sourceIndex = children.index(item);
+ umbSortContextInternal.sourceScope = $(container.el[0]).scope();
+ umbSortContextInternal.sourceContainer = container;
+
+ //current.item = ngModel.$modelValue.splice(current.index, 1)[0];
+
+ var pointer = container.rootGroup.pointer;
+ adjustment = {
+ left: pointer.left - offset.left,
+ top: pointer.top - offset.top
+ };
+
+ _super(item, container);
+ };
+
+ element.sortable( scope.opts );
+ }
+ };
+
+ });
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbTabView
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+*
+* @restrict E
+**/
+
+angular.module("umbraco.directives")
+.directive('umbTabView', function($timeout, $log){
+ return {
+ restrict: 'E',
+ replace: true,
+ transclude: 'true',
+ templateUrl: 'views/directives/_obsolete/umb-tab-view.html'
+ };
+});
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbUploadDropzone
+* @deprecated
+* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
+*
+* @restrict E
+**/
+
+angular.module("umbraco.directives.html")
+ .directive('umbUploadDropzone', function(){
+ return {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/directives/_obsolete/umb-upload-dropzone.html'
+ };
+ });
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:navResize
+* @restrict A
+ *
+ * @description
+ * Handles how the navigation responds to window resizing and controls how the draggable resize panel works
+**/
+angular.module("umbraco.directives")
+ .directive('navResize', function (appState, eventsService, windowResizeListener) {
+ return {
+ restrict: 'A',
+ link: function (scope, element, attrs, ctrl) {
+
+ var minScreenSize = 1100;
+ var resizeEnabled = false;
+
+ function setTreeMode() {
+ appState.setGlobalState("showNavigation", appState.getGlobalState("isTablet") === false);
+ }
+
+ function enableResize() {
+ //only enable when the size is correct and it's not already enabled
+ if (!resizeEnabled && appState.getGlobalState("isTablet") === false) {
+ element.resizable(
+ {
+ containment: $("#mainwrapper"),
+ autoHide: true,
+ handles: "e",
+ alsoResize: ".navigation-inner-container",
+ resize: function(e, ui) {
+ var wrapper = $("#mainwrapper");
+ var contentPanel = $("#contentwrapper");
+ var umbNotification = $("#umb-notifications-wrapper");
+ var apps = $("#applications");
+ var bottomBar = contentPanel.find(".umb-bottom-bar");
+ var navOffeset = $("#navOffset");
+
+ var leftPanelWidth = ui.element.width() + apps.width();
+
+ contentPanel.css({ left: leftPanelWidth });
+ bottomBar.css({ left: leftPanelWidth });
+ umbNotification.css({ left: leftPanelWidth });
+
+ navOffeset.css({ "margin-left": ui.element.outerWidth() });
+ },
+ stop: function (e, ui) {
+
+ }
+ });
+
+ resizeEnabled = true;
+ }
+ }
+
+ function resetResize() {
+ if (resizeEnabled) {
+ //kill the resize
+ element.resizable("destroy");
+ element.css("width", "");
+
+ var navInnerContainer = element.find(".navigation-inner-container");
+
+ navInnerContainer.css("width", "");
+ $("#contentwrapper").css("left", "");
+ $("#umb-notifications-wrapper").css("left", "");
+ $("#navOffset").css("margin-left", "");
+
+ resizeEnabled = false;
+ }
+ }
+
+ var evts = [];
+
+ //Listen for global state changes
+ evts.push(eventsService.on("appState.globalState.changed", function (e, args) {
+ if (args.key === "showNavigation") {
+ if (args.value === false) {
+ resetResize();
+ }
+ else {
+ enableResize();
+ }
+ }
+ }));
+
+ var resizeCallback = function(size) {
+ //set the global app state
+ appState.setGlobalState("isTablet", (size.width <= minScreenSize));
+ setTreeMode();
+ };
+
+ windowResizeListener.register(resizeCallback);
+
+ //ensure to unregister from all events and kill jquery plugins
+ scope.$on('$destroy', function () {
+ windowResizeListener.unregister(resizeCallback);
+ for (var e in evts) {
+ eventsService.unsubscribe(evts[e]);
+ }
+ var navInnerContainer = element.find(".navigation-inner-container");
+ navInnerContainer.resizable("destroy");
+ });
+
+ //init
+ //set the global app state
+ appState.setGlobalState("isTablet", ($(window).width() <= minScreenSize));
+ setTreeMode();
+ }
+ };
+ });
+
+angular.module("umbraco.directives")
+.directive('sectionIcon', function ($compile, iconHelper) {
+ return {
+ restrict: 'E',
+ replace: true,
+
+ link: function (scope, element, attrs) {
+
+ var icon = attrs.icon;
+
+ if (iconHelper.isLegacyIcon(icon)) {
+ //its a known legacy icon, convert to a new one
+ element.html("");
+ }
+ else if (iconHelper.isFileBasedIcon(icon)) {
+ var convert = iconHelper.convertFromLegacyImage(icon);
+ if(convert){
+ element.html("");
+ }else{
+ element.html("");
+ }
+ //it's a file, normally legacy so look in the icon tray images
+ }
+ else {
+ //it's normal
+ element.html("");
+ }
+ }
+ };
+});
+angular.module("umbraco.directives")
+.directive('umbContextMenu', function (navigationService) {
+ return {
+ scope: {
+ menuDialogTitle: "@",
+ currentSection: "@",
+ currentNode: "=",
+ menuActions: "="
+ },
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/application/umb-contextmenu.html',
+ link: function (scope, element, attrs, ctrl) {
+
+ //adds a handler to the context menu item click, we need to handle this differently
+ //depending on what the menu item is supposed to do.
+ scope.executeMenuItem = function (action) {
+ navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection);
+ };
+ }
+ };
+});
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbNavigation
+* @restrict E
+**/
+function umbNavigationDirective() {
+ return {
+ restrict: "E", // restrict to an element
+ replace: true, // replace the html element with the template
+ templateUrl: 'views/components/application/umb-navigation.html'
+ };
+}
+
+angular.module('umbraco.directives').directive("umbNavigation", umbNavigationDirective);
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbSections
+* @restrict E
+**/
+function sectionsDirective($timeout, $window, navigationService, treeService, sectionResource, appState, eventsService, $location) {
+ return {
+ restrict: "E", // restrict to an element
+ replace: true, // replace the html element with the template
+ templateUrl: 'views/components/application/umb-sections.html',
+ link: function (scope, element, attr, ctrl) {
+
+ //setup scope vars
+ scope.maxSections = 7;
+ scope.overflowingSections = 0;
+ scope.sections = [];
+ scope.currentSection = appState.getSectionState("currentSection");
+ scope.showTray = false; //appState.getGlobalState("showTray");
+ scope.stickyNavigation = appState.getGlobalState("stickyNavigation");
+ scope.needTray = false;
+ scope.trayAnimation = function() {
+ if (scope.showTray) {
+ return 'slide';
+ }
+ else if (scope.showTray === false) {
+ return 'slide';
+ }
+ else {
+ return '';
+ }
+ };
+
+ function loadSections(){
+ sectionResource.getSections()
+ .then(function (result) {
+ scope.sections = result;
+ calculateHeight();
+ });
+ }
+
+ function calculateHeight(){
+ $timeout(function(){
+ //total height minus room for avatar and help icon
+ var height = $(window).height()-200;
+ scope.totalSections = scope.sections.length;
+ scope.maxSections = Math.floor(height / 70);
+ scope.needTray = false;
+
+ if(scope.totalSections > scope.maxSections){
+ scope.needTray = true;
+ scope.overflowingSections = scope.maxSections - scope.totalSections;
+ }
+ });
+ }
+
+ var evts = [];
+
+ //Listen for global state changes
+ evts.push(eventsService.on("appState.globalState.changed", function(e, args) {
+ if (args.key === "showTray") {
+ scope.showTray = args.value;
+ }
+ if (args.key === "stickyNavigation") {
+ scope.stickyNavigation = args.value;
+ }
+ }));
+
+ evts.push(eventsService.on("appState.sectionState.changed", function(e, args) {
+ if (args.key === "currentSection") {
+ scope.currentSection = args.value;
+ }
+ }));
+
+ evts.push(eventsService.on("app.reInitialize", function(e, args) {
+ //re-load the sections if we're re-initializing (i.e. package installed)
+ loadSections();
+ }));
+
+ //ensure to unregister from all events!
+ scope.$on('$destroy', function () {
+ for (var e in evts) {
+ eventsService.unsubscribe(evts[e]);
+ }
+ });
+
+ //on page resize
+ window.onresize = calculateHeight;
+
+ scope.avatarClick = function(){
+
+ if(scope.helpDialog) {
+ closeHelpDialog();
+ }
+
+ if(!scope.userDialog) {
+ scope.userDialog = {
+ view: "user",
+ show: true,
+ close: function(oldModel) {
+ closeUserDialog();
+ }
+ };
+ } else {
+ closeUserDialog();
+ }
+
+ };
+
+ function closeUserDialog() {
+ scope.userDialog.show = false;
+ scope.userDialog = null;
+ }
+
+ scope.helpClick = function(){
+
+ if(scope.userDialog) {
+ closeUserDialog();
+ }
+
+ if(!scope.helpDialog) {
+ scope.helpDialog = {
+ view: "help",
+ show: true,
+ close: function(oldModel) {
+ closeHelpDialog();
+ }
+ };
+ } else {
+ closeHelpDialog();
+ }
+
+ };
+
+ function closeHelpDialog() {
+ scope.helpDialog.show = false;
+ scope.helpDialog = null;
+ }
+
+ scope.sectionClick = function (event, section) {
+
+ if (event.ctrlKey ||
+ event.shiftKey ||
+ event.metaKey || // apple
+ (event.button && event.button === 1) // middle click, >IE9 + everyone else
+ ) {
+ return;
+ }
+
+ if (scope.userDialog) {
+ closeUserDialog();
+ }
+ if (scope.helpDialog) {
+ closeHelpDialog();
+ }
+
+ navigationService.hideSearch();
+ navigationService.showTree(section.alias);
+ $location.path("/" + section.alias);
+ };
+
+ scope.sectionDblClick = function(section){
+ navigationService.reloadSection(section.alias);
+ };
+
+ scope.trayClick = function () {
+ // close dialogs
+ if (scope.userDialog) {
+ closeUserDialog();
+ }
+ if (scope.helpDialog) {
+ closeHelpDialog();
+ }
+
+ if (appState.getGlobalState("showTray") === true) {
+ navigationService.hideTray();
+ } else {
+ navigationService.showTray();
+ }
+ };
+
+ loadSections();
+
+ }
+ };
+}
+
+angular.module('umbraco.directives').directive("umbSections", sectionsDirective);
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbButton
+@restrict E
+@scope
+
+@description
+Use this directive to render an umbraco button. The directive can be used to generate all types of buttons, set type, style, translation, shortcut and much more.
+
+
+
+@param {callback} action The button action which should be performed when the button is clicked.
+@param {string=} href Url/Path to navigato to.
+@param {string=} type Set the button type ("button" or "submit").
+@param {string=} buttonStyle Set the style of the button. The directive uses the default bootstrap styles ("primary", "info", "success", "warning", "danger", "inverse", "link").
+@param {string=} state Set a progress state on the button ("init", "busy", "success", "error").
+@param {string=} shortcut Set a keyboard shortcut for the button ("ctrl+c").
+@param {string=} label Set the button label.
+@param {string=} labelKey Set a localization key to make a multi lingual button ("general_buttonText").
+@param {string=} icon Set a button icon. Can only be used when buttonStyle is "link".
+@param {boolean=} disabled Set to true to disable the button.
+**/
+
+(function() {
+ 'use strict';
+
+ function ButtonDirective($timeout) {
+
+ function link(scope, el, attr, ctrl) {
+
+ scope.style = null;
+
+ function activate() {
+
+ if (!scope.state) {
+ scope.state = "init";
+ }
+
+ if (scope.buttonStyle) {
+ scope.style = "btn-" + scope.buttonStyle;
+ }
+
+ }
+
+ activate();
+
+ var unbindStateWatcher = scope.$watch('state', function(newValue, oldValue) {
+
+ if (newValue === 'success' || newValue === 'error') {
+ $timeout(function() {
+ scope.state = 'init';
+ }, 2000);
+ }
+
+ });
+
+ scope.$on('$destroy', function() {
+ unbindStateWatcher();
+ });
+
+ }
+
+ var directive = {
+ transclude: true,
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/buttons/umb-button.html',
+ link: link,
+ scope: {
+ action: "&?",
+ href: "@?",
+ type: "@",
+ buttonStyle: "@?",
+ state: "=?",
+ shortcut: "@?",
+ shortcutWhenHidden: "@",
+ label: "@?",
+ labelKey: "@?",
+ icon: "@?",
+ disabled: "="
+ }
+ };
+
+ return directive;
+
+ }
+
+ angular.module('umbraco.directives').directive('umbButton', ButtonDirective);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbButtonGroup
+@restrict E
+@scope
+
+@description
+Use this directive to render a button with a dropdown of alternative actions.
+
+
+ labekKey
+ (string) -
+ Set a localization key to make a multi lingual button ("general_buttonText").
+
+
+ hotKey
+ (array) -
+ Set a keyboard shortcut for the button ("ctrl+c").
+
+
+ hotKeyWhenHidden
+ (boolean) -
+ As a default the hotkeys only works on elements visible in the UI. Set to true to set a hotkey on the hidden sub buttons.
+
+
+ handler
+ (callback) -
+ Set a callback to handle button click events.
+
+
+
+@param {object} defaultButton The model of the default button.
+@param {array} subButtons Array of sub buttons.
+@param {string=} state Set a progress state on the button ("init", "busy", "success", "error").
+@param {string=} direction Set the direction of the dropdown ("up", "down").
+@param {string=} float Set the float of the dropdown. ("left", "right").
+**/
+
+(function() {
+ 'use strict';
+
+ function ButtonGroupDirective() {
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/buttons/umb-button-group.html',
+ scope: {
+ defaultButton: "=",
+ subButtons: "=",
+ state: "=?",
+ direction: "@?",
+ float: "@?"
+ }
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbButtonGroup', ButtonGroupDirective);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbEditorSubHeader
+@restrict E
+
+@description
+Use this directive to construct a sub header in the main editor window.
+The sub header is sticky and will follow along down the page when scrolling.
+
+
';
+
+ element.replaceWith(template);
+
+ return function(scope, elem, attr, controller) {
+
+ //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should
+ // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover
+ // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the
+ // entire tree again since we already still have it in memory. Of course if the section is different we will
+ // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times
+ // since it saves on data retreival and DOM processing.
+ var lastSection = "";
+
+ //setup a default internal handler
+ if (!scope.eventhandler) {
+ scope.eventhandler = $({});
+ }
+
+ //flag to enable/disable delete animations
+ var deleteAnimations = false;
+
+
+ /** Helper function to emit tree events */
+ function emitEvent(eventName, args) {
+ if (scope.eventhandler) {
+ $(scope.eventhandler).trigger(eventName, args);
+ }
+ }
+
+ /** This will deleteAnimations to true after the current digest */
+ function enableDeleteAnimations() {
+ //do timeout so that it re-enables them after this digest
+ $timeout(function () {
+ //enable delete animations
+ deleteAnimations = true;
+ }, 0, false);
+ }
+
+
+ /*this is the only external interface a tree has */
+ function setupExternalEvents() {
+ if (scope.eventhandler) {
+
+ scope.eventhandler.clearCache = function(section) {
+ treeService.clearCache({ section: section });
+ };
+
+ scope.eventhandler.load = function(section) {
+ scope.section = section;
+ loadTree();
+ };
+
+ scope.eventhandler.reloadNode = function(node) {
+
+ if (!node) {
+ node = scope.currentNode;
+ }
+
+ if (node) {
+ scope.loadChildren(node, true);
+ }
+ };
+
+ /**
+ Used to do the tree syncing. If the args.tree is not specified we are assuming it has been
+ specified previously using the _setActiveTreeType
+ */
+ scope.eventhandler.syncTree = function(args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.path) {
+ throw "args.path cannot be null";
+ }
+
+ var deferred = $q.defer();
+
+ //this is super complex but seems to be working in other places, here we're listening for our
+ // own events, once the tree is sycned we'll resolve our promise.
+ scope.eventhandler.one("treeSynced", function (e, syncArgs) {
+ deferred.resolve(syncArgs);
+ });
+
+ //this should normally be set unless it is being called from legacy
+ // code, so set the active tree type before proceeding.
+ if (args.tree) {
+ loadActiveTree(args.tree);
+ }
+
+ if (angular.isString(args.path)) {
+ args.path = args.path.replace('"', '').split(',');
+ }
+
+ //reset current node selection
+ //scope.currentNode = null;
+
+ //Filter the path for root node ids (we don't want to pass in -1 or 'init')
+
+ args.path = _.filter(args.path, function (item) { return (item !== "init" && item !== "-1"); });
+
+ //Once those are filtered we need to check if the current user has a special start node id,
+ // if they do, then we're going to trim the start of the array for anything found from that start node
+ // and previous so that the tree syncs properly. The tree syncs from the top down and if there are parts
+ // of the tree's path in there that don't actually exist in the dom/model then syncing will not work.
+
+ userService.getCurrentUser().then(function(userData) {
+
+ var startNodes = [userData.startContentId, userData.startMediaId];
+ _.each(startNodes, function (i) {
+ var found = _.find(args.path, function (p) {
+ return String(p) === String(i);
+ });
+ if (found) {
+ args.path = args.path.splice(_.indexOf(args.path, found));
+ }
+ });
+
+
+ loadPath(args.path, args.forceReload, args.activate);
+
+ });
+
+
+
+ return deferred.promise;
+ };
+
+ /**
+ Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
+ have to set an active tree and then sync, the new API does this in one method by using syncTree.
+ loadChildren is optional but if it is set, it will set the current active tree and load the root
+ node's children - this is synonymous with the legacy refreshTree method - again should not be used
+ and should only be used for the legacy code to work.
+ */
+ scope.eventhandler._setActiveTreeType = function(treeAlias, loadChildren) {
+ loadActiveTree(treeAlias, loadChildren);
+ };
+ }
+ }
+
+
+ //helper to load a specific path on the active tree as soon as its ready
+ function loadPath(path, forceReload, activate) {
+
+ if (scope.activeTree) {
+ syncTree(scope.activeTree, path, forceReload, activate);
+ }
+ else {
+ scope.eventhandler.one("activeTreeLoaded", function (e, args) {
+ syncTree(args.tree, path, forceReload, activate);
+ });
+ }
+ }
+
+
+ //given a tree alias, this will search the current section tree for the specified tree alias and
+ //set that to the activeTree
+ //NOTE: loadChildren is ONLY used for legacy purposes, do not use this when syncing the tree as it will cause problems
+ // since there will be double request and event handling operations.
+ function loadActiveTree(treeAlias, loadChildren) {
+ if (!treeAlias) {
+ return;
+ }
+
+ scope.activeTree = undefined;
+
+ function doLoad(tree) {
+ var childrenAndSelf = [tree].concat(tree.children);
+ scope.activeTree = _.find(childrenAndSelf, function (node) {
+ if(node && node.metaData && node.metaData.treeAlias) {
+ return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase();
+ }
+ return false;
+ });
+
+ if (!scope.activeTree) {
+ throw "Could not find the tree " + treeAlias + ", activeTree has not been set";
+ }
+
+ //This is only used for the legacy tree method refreshTree!
+ if (loadChildren) {
+ scope.activeTree.expanded = true;
+ scope.loadChildren(scope.activeTree, false).then(function() {
+ emitEvent("activeTreeLoaded", { tree: scope.activeTree });
+ });
+ }
+ else {
+ emitEvent("activeTreeLoaded", { tree: scope.activeTree });
+ }
+ }
+
+ if (scope.tree) {
+ doLoad(scope.tree.root);
+ }
+ else {
+ scope.eventhandler.one("treeLoaded", function(e, args) {
+ doLoad(args.tree.root);
+ });
+ }
+ }
+
+
+ /** Method to load in the tree data */
+
+ function loadTree() {
+ if (!scope.loading && scope.section) {
+ scope.loading = true;
+
+ //anytime we want to load the tree we need to disable the delete animations
+ deleteAnimations = false;
+
+ //default args
+ var args = { section: scope.section, tree: scope.treealias, cacheKey: scope.cachekey, isDialog: scope.isdialog ? scope.isdialog : false };
+
+ //add the extra query string params if specified
+ if (scope.customtreeparams) {
+ args["queryString"] = scope.customtreeparams;
+ }
+
+ treeService.getTree(args)
+ .then(function(data) {
+ //set the data once we have it
+ scope.tree = data;
+
+ enableDeleteAnimations();
+
+ scope.loading = false;
+
+ //set the root as the current active tree
+ scope.activeTree = scope.tree.root;
+ emitEvent("treeLoaded", { tree: scope.tree });
+ emitEvent("treeNodeExpanded", { tree: scope.tree, node: scope.tree.root, children: scope.tree.root.children });
+
+ }, function(reason) {
+ scope.loading = false;
+ notificationsService.error("Tree Error", reason);
+ });
+ }
+ }
+
+ /** syncs the tree, the treeNode can be ANY tree node in the tree that requires syncing */
+ function syncTree(treeNode, path, forceReload, activate) {
+
+ deleteAnimations = false;
+
+ treeService.syncTree({
+ node: treeNode,
+ path: path,
+ forceReload: forceReload
+ }).then(function (data) {
+
+ if (activate === undefined || activate === true) {
+ scope.currentNode = data;
+ }
+
+ emitEvent("treeSynced", { node: data, activate: activate });
+
+ enableDeleteAnimations();
+ });
+
+ }
+
+ scope.selectEnabledNodeClass = function (node) {
+ return node ?
+ node.selected ?
+ 'icon umb-tree-icon sprTree icon-check blue temporary' :
+ '' :
+ '';
+ };
+
+ /** method to set the current animation for the node.
+ * This changes dynamically based on if we are changing sections or just loading normal tree data.
+ * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations.
+ */
+ scope.animation = function() {
+ if (deleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) {
+ return { leave: 'tree-node-delete-leave' };
+ }
+ else {
+ return {};
+ }
+ };
+
+ /* helper to force reloading children of a tree node */
+ scope.loadChildren = function(node, forceReload) {
+ var deferred = $q.defer();
+
+ //emit treeNodeExpanding event, if a callback object is set on the tree
+ emitEvent("treeNodeExpanding", { tree: scope.tree, node: node });
+
+ //standardising
+ if (!node.children) {
+ node.children = [];
+ }
+
+ if (forceReload || (node.hasChildren && node.children.length === 0)) {
+ //get the children from the tree service
+ treeService.loadNodeChildren({ node: node, section: scope.section })
+ .then(function(data) {
+ //emit expanded event
+ emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data });
+
+ enableDeleteAnimations();
+
+ deferred.resolve(data);
+ });
+ }
+ else {
+ emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: node.children });
+ node.expanded = true;
+
+ enableDeleteAnimations();
+
+ deferred.resolve(node.children);
+ }
+
+ return deferred.promise;
+ };
+
+ /**
+ Method called when the options button next to the root node is called.
+ The tree doesnt know about this, so it raises an event to tell the parent controller
+ about it.
+ */
+ scope.options = function(n, ev) {
+ emitEvent("treeOptionsClick", { element: elem, node: n, event: ev });
+ };
+
+ /**
+ Method called when an item is clicked in the tree, this passes the
+ DOM element, the tree node object and the original click
+ and emits it as a treeNodeSelect element if there is a callback object
+ defined on the tree
+ */
+ scope.select = function (n, ev) {
+ //on tree select we need to remove the current node -
+ // whoever handles this will need to make sure the correct node is selected
+ //reset current node selection
+ scope.currentNode = null;
+
+ emitEvent("treeNodeSelect", { element: elem, node: n, event: ev });
+ };
+
+ scope.altSelect = function(n, ev) {
+ emitEvent("treeNodeAltSelect", { element: elem, tree: scope.tree, node: n, event: ev });
+ };
+
+ //watch for section changes
+ scope.$watch("section", function(newVal, oldVal) {
+
+ if (!scope.tree) {
+ loadTree();
+ }
+
+ if (!newVal) {
+ //store the last section loaded
+ lastSection = oldVal;
+ }
+ else if (newVal !== oldVal && newVal !== lastSection) {
+ //only reload the tree data and Dom if the newval is different from the old one
+ // and if the last section loaded is different from the requested one.
+ loadTree();
+
+ //store the new section to be loaded as the last section
+ //clear any active trees to reset lookups
+ lastSection = newVal;
+ }
+ });
+
+ setupExternalEvents();
+ loadTree();
+ };
+ }
+ };
+}
+
+angular.module("umbraco.directives").directive('umbTree', umbTreeDirective);
+
+/**
+ * @ngdoc directive
+ * @name umbraco.directives.directive:umbTreeItem
+ * @element li
+ * @function
+ *
+ * @description
+ * Renders a list item, representing a single node in the tree.
+ * Includes element to toggle children, and a menu toggling button
+ *
+ * **note:** This directive is only used internally in the umbTree directive
+ *
+ * @example
+
+
+
+
+
+ */
+angular.module("umbraco.directives")
+.directive('umbTreeItem', function ($compile, $http, $templateCache, $interpolate, $log, $location, $rootScope, $window, treeService, $timeout, localizationService) {
+ return {
+ restrict: 'E',
+ replace: true,
+
+ scope: {
+ section: '@',
+ eventhandler: '=',
+ currentNode: '=',
+ node: '=',
+ tree: '='
+ },
+
+ //TODO: Remove more of the binding from this template and move the DOM manipulation to be manually done in the link function,
+ // this will greatly improve performance since there's potentially a lot of nodes being rendered = a LOT of watches!
+
+ template: '
' +
+ '
' +
+ //NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog
+ //'' +
+ ' ' +
+ '' +
+ '' +
+ //NOTE: These are the 'option' elipses
+ '' +
+ '
' +
+ '
' +
+ '
',
+
+ link: function (scope, element, attrs) {
+
+ localizationService.localize("general_search").then(function (value) {
+ scope.searchAltText = value;
+ });
+
+ //flag to enable/disable delete animations, default for an item is true
+ var deleteAnimations = true;
+
+ // Helper function to emit tree events
+ function emitEvent(eventName, args) {
+
+ if (scope.eventhandler) {
+ $(scope.eventhandler).trigger(eventName, args);
+ }
+ }
+
+ // updates the node's DOM/styles
+ function setupNodeDom(node, tree) {
+
+ //get the first div element
+ element.children(":first")
+ //set the padding
+ .css("padding-left", (node.level * 20) + "px");
+
+ //toggle visibility of last 'ins' depending on children
+ //visibility still ensure the space is "reserved", so both nodes with and without children are aligned.
+ if (!node.hasChildren) {
+ element.find("ins").last().css("visibility", "hidden");
+ }
+ else {
+ element.find("ins").last().css("visibility", "visible");
+ }
+
+ var icon = element.find("i:first");
+ icon.addClass(node.cssClass);
+ icon.attr("title", node.routePath);
+
+ element.find("a:first").text(node.name);
+
+ if (!node.menuUrl) {
+ element.find("a.umb-options").remove();
+ }
+
+ if (node.style) {
+ element.find("i:first").attr("style", node.style);
+ }
+ }
+
+ //This will deleteAnimations to true after the current digest
+ function enableDeleteAnimations() {
+ //do timeout so that it re-enables them after this digest
+ $timeout(function () {
+ //enable delete animations
+ deleteAnimations = true;
+ }, 0, false);
+ }
+
+ /** Returns the css classses assigned to the node (div element) */
+ scope.getNodeCssClass = function (node) {
+ if (!node) {
+ return '';
+ }
+ var css = [];
+ if (node.cssClasses) {
+ _.each(node.cssClasses, function(c) {
+ css.push(c);
+ });
+ }
+ if (node.selected) {
+ css.push("umb-tree-node-checked");
+ }
+ return css.join(" ");
+ };
+
+ //add a method to the node which we can use to call to update the node data if we need to ,
+ // this is done by sync tree, we don't want to add a $watch for each node as that would be crazy insane slow
+ // so we have to do this
+ scope.node.updateNodeData = function (newNode) {
+ _.extend(scope.node, newNode);
+ //now update the styles
+ setupNodeDom(scope.node, scope.tree);
+ };
+
+ /**
+ Method called when the options button next to a node is called
+ In the main tree this opens the menu, but internally the tree doesnt
+ know about this, so it simply raises an event to tell the parent controller
+ about it.
+ */
+ scope.options = function (n, ev) {
+ emitEvent("treeOptionsClick", { element: element, tree: scope.tree, node: n, event: ev });
+ };
+
+ /**
+ Method called when an item is clicked in the tree, this passes the
+ DOM element, the tree node object and the original click
+ and emits it as a treeNodeSelect element if there is a callback object
+ defined on the tree
+ */
+ scope.select = function (n, ev) {
+ if (ev.ctrlKey ||
+ ev.shiftKey ||
+ ev.metaKey || // apple
+ (ev.button && ev.button === 1) // middle click, >IE9 + everyone else
+ ) {
+ return;
+ }
+
+ emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev });
+ ev.preventDefault();
+ };
+
+ /**
+ Method called when an item is right-clicked in the tree, this passes the
+ DOM element, the tree node object and the original click
+ and emits it as a treeNodeSelect element if there is a callback object
+ defined on the tree
+ */
+ scope.altSelect = function (n, ev) {
+ emitEvent("treeNodeAltSelect", { element: element, tree: scope.tree, node: n, event: ev });
+ };
+
+ /** method to set the current animation for the node.
+ * This changes dynamically based on if we are changing sections or just loading normal tree data.
+ * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations.
+ */
+ scope.animation = function () {
+ if (scope.node.showHideAnimation) {
+ return scope.node.showHideAnimation;
+ }
+ if (deleteAnimations && scope.node.expanded) {
+ return { leave: 'tree-node-delete-leave' };
+ }
+ else {
+ return {};
+ }
+ };
+
+ /**
+ Method called when a node in the tree is expanded, when clicking the arrow
+ takes the arrow DOM element and node data as parameters
+ emits treeNodeCollapsing event if already expanded and treeNodeExpanding if collapsed
+ */
+ scope.load = function (node) {
+ if (node.expanded) {
+ deleteAnimations = false;
+ emitEvent("treeNodeCollapsing", { tree: scope.tree, node: node, element: element });
+ node.expanded = false;
+ }
+ else {
+ scope.loadChildren(node, false);
+ }
+ };
+
+ /* helper to force reloading children of a tree node */
+ scope.loadChildren = function (node, forceReload) {
+ //emit treeNodeExpanding event, if a callback object is set on the tree
+ emitEvent("treeNodeExpanding", { tree: scope.tree, node: node });
+
+ if (node.hasChildren && (forceReload || !node.children || (angular.isArray(node.children) && node.children.length === 0))) {
+ //get the children from the tree service
+ treeService.loadNodeChildren({ node: node, section: scope.section })
+ .then(function (data) {
+ //emit expanded event
+ emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data });
+ enableDeleteAnimations();
+ });
+ }
+ else {
+ emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: node.children });
+ node.expanded = true;
+ enableDeleteAnimations();
+ }
+ };
+
+ //if the current path contains the node id, we will auto-expand the tree item children
+
+ setupNodeDom(scope.node, scope.tree);
+
+ var template = '
';
+ var newElement = angular.element(template);
+ $compile(newElement)(scope);
+ element.append(newElement);
+
+ }
+ };
+});
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbTreeSearchBox
+* @function
+* @element ANY
+* @restrict E
+**/
+function treeSearchBox(localizationService, searchService, $q) {
+ return {
+ scope: {
+ searchFromId: "@",
+ searchFromName: "@",
+ showSearch: "@",
+ section: "@",
+ hideSearchCallback: "=",
+ searchCallback: "="
+ },
+ restrict: "E", // restrict to an element
+ replace: true, // replace the html element with the template
+ templateUrl: 'views/components/tree/umb-tree-search-box.html',
+ link: function (scope, element, attrs, ctrl) {
+
+ scope.term = "";
+ scope.hideSearch = function() {
+ scope.term = "";
+ scope.hideSearchCallback();
+ };
+
+ localizationService.localize("general_typeToSearch").then(function (value) {
+ scope.searchPlaceholderText = value;
+ });
+
+ if (!scope.showSearch) {
+ scope.showSearch = "false";
+ }
+
+ //used to cancel any request in progress if another one needs to take it's place
+ var canceler = null;
+
+ function performSearch() {
+ if (scope.term) {
+ scope.results = [];
+
+ //a canceler exists, so perform the cancelation operation and reset
+ if (canceler) {
+ canceler.resolve();
+ canceler = $q.defer();
+ }
+ else {
+ canceler = $q.defer();
+ }
+
+ var searchArgs = {
+ term: scope.term,
+ canceler: canceler
+ };
+
+ //append a start node context if there is one
+ if (scope.searchFromId) {
+ searchArgs["searchFrom"] = scope.searchFromId;
+ }
+
+ searcher(searchArgs).then(function (data) {
+ scope.searchCallback(data);
+ //set back to null so it can be re-created
+ canceler = null;
+ });
+ }
+ }
+
+ scope.$watch("term", _.debounce(function(newVal, oldVal) {
+ scope.$apply(function() {
+ if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
+ performSearch();
+ }
+ });
+ }, 200));
+
+ var searcher = searchService.searchContent;
+ //search
+ if (scope.section === "member") {
+ searcher = searchService.searchMembers;
+ }
+ else if (scope.section === "media") {
+ searcher = searchService.searchMedia;
+ }
+ }
+ };
+}
+angular.module('umbraco.directives').directive("umbTreeSearchBox", treeSearchBox);
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbTreeSearchResults
+* @function
+* @element ANY
+* @restrict E
+**/
+function treeSearchResults() {
+ return {
+ scope: {
+ results: "=",
+ selectResultCallback: "="
+ },
+ restrict: "E", // restrict to an element
+ replace: true, // replace the html element with the template
+ templateUrl: 'views/components/tree/umb-tree-search-results.html',
+ link: function (scope, element, attrs, ctrl) {
+
+ }
+ };
+}
+angular.module('umbraco.directives').directive("umbTreeSearchResults", treeSearchResults);
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbGenerateAlias
+@restrict E
+@scope
+
+@description
+Use this directive to generate a camelCased umbraco alias.
+When the aliasFrom value is changed the directive will get a formatted alias from the server and update the alias model. If "enableLock" is set to true
+the directive will use {@link umbraco.directives.directive:umbLockedField umbLockedField} to lock and unlock the alias.
+
+
+
+@param {array} selectedChildren (binding): Array of selected children.
+@param {array} availableChildren (binding: Array of items available for selection.
+@param {string} parentName (binding): The parent name.
+@param {string} parentIcon (binding): The parent icon.
+@param {number} parentId (binding): The parent id.
+@param {callback} onRemove (binding): Callback when the remove button is clicked on an item.
+
The callback returns:
+
+
child: The selected item.
+
$index: The selected item index.
+
+@param {callback} onAdd (binding): Callback when the add button is clicked.
+
Use this directive to toggle a confirmation prompt for an action.
+The prompt consists of a checkmark and a cross to confirm or cancel the action.
+The prompt can be opened in four direction up, down, left or right.
+
+@param {array} content (binding): Array of content items.
+@param {array=} contentProperties (binding): Array of content item properties to include in the item. If left empty the item will only show the item icon and name.
+@param {callback=} onClick (binding): Callback method to handle click events on the content item.
+
The callback returns:
+
+
item: The clicked item
+
$event: The select event
+
$index: The item index
+
+@param {callback=} onClickName (binding): Callback method to handle click events on the checkmark icon.
+
The callback returns:
+
+
item: The selected item
+
$event: The select event
+
$index: The item index
+
+**/
+
+(function() {
+ 'use strict';
+
+ function ContentGridDirective() {
+
+ function link(scope, el, attr, ctrl) {
+
+ scope.clickItem = function(item, $event, $index) {
+ if(scope.onClick) {
+ scope.onClick(item, $event, $index);
+ }
+ };
+
+ scope.clickItemName = function(item, $event, $index) {
+ if(scope.onClickName) {
+ scope.onClickName(item, $event, $index);
+ }
+ };
+
+ }
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-content-grid.html',
+ scope: {
+ content: '=',
+ contentProperties: "=",
+ onClick: "=",
+ onClickName: "="
+ },
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbContentGrid', ContentGridDirective);
+
+})();
+
+(function() {
+ 'use strict';
+
+ function UmbDisableFormValidation() {
+
+ var directive = {
+ restrict: 'A',
+ require: '?form',
+ link: function (scope, elm, attrs, ctrl) {
+ //override the $setValidity function of the form to disable validation
+ ctrl.$setValidity = function () { };
+ }
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbDisableFormValidation', UmbDisableFormValidation);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbEmptyState
+@restrict E
+@scope
+
+@description
+Use this directive to show an empty state message.
+
+
Markup example
+
+
+
+
+ // Empty state content
+
+
+
+
+
+@param {string=} size Set the size of the text ("small", "large").
+@param {string=} position Set the position of the text ("center").
+**/
+
+(function() {
+ 'use strict';
+
+ function EmptyStateDirective() {
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ transclude: true,
+ templateUrl: 'views/components/umb-empty-state.html',
+ scope: {
+ size: '@',
+ position: '@'
+ }
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbEmptyState', EmptyStateDirective);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbFolderGrid
+@restrict E
+@scope
+
+@description
+Use this directive to generate a list of folders presented as a flexbox grid.
+
+
+
+@param {array} folders (binding): Array of folders
+@param {callback=} onClick (binding): Callback method to handle click events on the folder.
+
The callback returns:
+
+
folder: The selected folder
+
+@param {callback=} onSelect (binding): Callback method to handle click events on the checkmark icon.
+
The callback returns:
+
+
folder: The selected folder
+
$event: The select event
+
$index: The folder index
+
+**/
+
+(function() {
+ 'use strict';
+
+ function FolderGridDirective() {
+
+ function link(scope, el, attr, ctrl) {
+
+ scope.clickFolder = function(folder, $event, $index) {
+ if(scope.onClick) {
+ scope.onClick(folder, $event, $index);
+ }
+ };
+
+ scope.clickFolderName = function(folder, $event, $index) {
+ if(scope.onClickName) {
+ scope.onClickName(folder, $event, $index);
+ $event.stopPropagation();
+ }
+ };
+
+ }
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-folder-grid.html',
+ scope: {
+ folders: '=',
+ onClick: "=",
+ onClickName: "="
+ },
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbFolderGrid', FolderGridDirective);
+
+})();
+
+(function() {
+ 'use strict';
+
+ function GridSelector() {
+
+ function link(scope, el, attr, ctrl) {
+
+ var eventBindings = [];
+ scope.dialogModel = {};
+ scope.showDialog = false;
+ scope.itemLabel = "";
+
+ // set default item name
+ if(!scope.itemName){
+ scope.itemLabel = "item";
+ } else {
+ scope.itemLabel = scope.itemName;
+ }
+
+ scope.removeItem = function(selectedItem) {
+ var selectedItemIndex = scope.selectedItems.indexOf(selectedItem);
+ scope.selectedItems.splice(selectedItemIndex, 1);
+ };
+
+ scope.removeDefaultItem = function() {
+
+ // it will be the last item so we can clear the array
+ scope.selectedItems = [];
+
+ // remove as default item
+ scope.defaultItem = null;
+
+ };
+
+ scope.openItemPicker = function($event){
+ scope.dialogModel = {
+ view: "itempicker",
+ title: "Choose " + scope.itemLabel,
+ availableItems: scope.availableItems,
+ selectedItems: scope.selectedItems,
+ event: $event,
+ show: true,
+ submit: function(model) {
+ scope.selectedItems.push(model.selectedItem);
+
+ // if no default item - set item as default
+ if(scope.defaultItem === null) {
+ scope.setAsDefaultItem(model.selectedItem);
+ }
+
+ scope.dialogModel.show = false;
+ scope.dialogModel = null;
+ }
+ };
+ };
+
+ scope.setAsDefaultItem = function(selectedItem) {
+
+ // clear default item
+ scope.defaultItem = {};
+
+ // set as default item
+ scope.defaultItem = selectedItem;
+ };
+
+ function updatePlaceholders() {
+
+ // update default item
+ if(scope.defaultItem !== null && scope.defaultItem.placeholder) {
+
+ scope.defaultItem.name = scope.name;
+
+ if(scope.alias !== null && scope.alias !== undefined) {
+ scope.defaultItem.alias = scope.alias;
+ }
+
+ }
+
+ // update selected items
+ angular.forEach(scope.selectedItems, function(selectedItem) {
+ if(selectedItem.placeholder) {
+
+ selectedItem.name = scope.name;
+
+ if(scope.alias !== null && scope.alias !== undefined) {
+ selectedItem.alias = scope.alias;
+ }
+
+ }
+ });
+
+ // update availableItems
+ angular.forEach(scope.availableItems, function(availableItem) {
+ if(availableItem.placeholder) {
+
+ availableItem.name = scope.name;
+
+ if(scope.alias !== null && scope.alias !== undefined) {
+ availableItem.alias = scope.alias;
+ }
+
+ }
+ });
+
+ }
+
+ function activate() {
+
+ // add watchers for updating placeholde name and alias
+ if(scope.updatePlaceholder) {
+ eventBindings.push(scope.$watch('name', function(newValue, oldValue){
+ updatePlaceholders();
+ }));
+
+ eventBindings.push(scope.$watch('alias', function(newValue, oldValue){
+ updatePlaceholders();
+ }));
+ }
+
+ }
+
+ activate();
+
+ // clean up
+ scope.$on('$destroy', function(){
+
+ // clear watchers
+ for(var e in eventBindings) {
+ eventBindings[e]();
+ }
+
+ });
+
+ }
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-grid-selector.html',
+ scope: {
+ name: "=",
+ alias: "=",
+ selectedItems: '=',
+ availableItems: "=",
+ defaultItem: "=",
+ itemName: "@",
+ updatePlaceholder: "="
+ },
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbGridSelector', GridSelector);
+
+})();
+
+(function() {
+ 'use strict';
+
+ function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, localizationService) {
+
+ function link(scope, el, attr, ctrl) {
+
+ var validationTranslated = "";
+ var tabNoSortOrderTranslated = "";
+
+ scope.sortingMode = false;
+ scope.toolbar = [];
+ scope.sortableOptionsGroup = {};
+ scope.sortableOptionsProperty = {};
+ scope.sortingButtonKey = "general_reorder";
+
+ function activate() {
+
+ setSortingOptions();
+
+ // set placeholder property on each group
+ if (scope.model.groups.length !== 0) {
+ angular.forEach(scope.model.groups, function(group) {
+ addInitProperty(group);
+ });
+ }
+
+ // add init tab
+ addInitGroup(scope.model.groups);
+
+ activateFirstGroup(scope.model.groups);
+
+ // localize texts
+ localizationService.localize("validation_validation").then(function(value) {
+ validationTranslated = value;
+ });
+
+ localizationService.localize("contentTypeEditor_tabHasNoSortOrder").then(function(value) {
+ tabNoSortOrderTranslated = value;
+ });
+ }
+
+ function setSortingOptions() {
+
+ scope.sortableOptionsGroup = {
+ distance: 10,
+ tolerance: "pointer",
+ opacity: 0.7,
+ scroll: true,
+ cursor: "move",
+ placeholder: "umb-group-builder__group-sortable-placeholder",
+ zIndex: 6000,
+ handle: ".umb-group-builder__group-handle",
+ items: ".umb-group-builder__group-sortable",
+ start: function(e, ui) {
+ ui.placeholder.height(ui.item.height());
+ },
+ stop: function(e, ui) {
+ updateTabsSortOrder();
+ },
+ };
+
+ scope.sortableOptionsProperty = {
+ distance: 10,
+ tolerance: "pointer",
+ connectWith: ".umb-group-builder__properties",
+ opacity: 0.7,
+ scroll: true,
+ cursor: "move",
+ placeholder: "umb-group-builder__property_sortable-placeholder",
+ zIndex: 6000,
+ handle: ".umb-group-builder__property-handle",
+ items: ".umb-group-builder__property-sortable",
+ start: function(e, ui) {
+ ui.placeholder.height(ui.item.height());
+ },
+ stop: function(e, ui) {
+ updatePropertiesSortOrder();
+ }
+ };
+
+ }
+
+ function updateTabsSortOrder() {
+
+ var first = true;
+ var prevSortOrder = 0;
+
+ scope.model.groups.map(function(group){
+
+ var index = scope.model.groups.indexOf(group);
+
+ if(group.tabState !== "init") {
+
+ // set the first not inherited tab to sort order 0
+ if(!group.inherited && first) {
+
+ // set the first tab sort order to 0 if prev is 0
+ if( prevSortOrder === 0 ) {
+ group.sortOrder = 0;
+ // when the first tab is inherited and sort order is not 0
+ } else {
+ group.sortOrder = prevSortOrder + 1;
+ }
+
+ first = false;
+
+ } else if(!group.inherited && !first) {
+
+ // find next group
+ var nextGroup = scope.model.groups[index + 1];
+
+ // if a groups is dropped in the middle of to groups with
+ // same sort order. Give it the dropped group same sort order
+ if( prevSortOrder === nextGroup.sortOrder ) {
+ group.sortOrder = prevSortOrder;
+ } else {
+ group.sortOrder = prevSortOrder + 1;
+ }
+
+ }
+
+ // store this tabs sort order as reference for the next
+ prevSortOrder = group.sortOrder;
+
+ }
+
+ });
+
+ }
+
+ function filterAvailableCompositions(selectedContentType, selecting) {
+
+ //selecting = true if the user has check the item, false if the user has unchecked the item
+
+ var selectedContentTypeAliases = selecting ?
+ //the user has selected the item so add to the current list
+ _.union(scope.compositionsDialogModel.compositeContentTypes, [selectedContentType.alias]) :
+ //the user has unselected the item so remove from the current list
+ _.reject(scope.compositionsDialogModel.compositeContentTypes, function(i) {
+ return i === selectedContentType.alias;
+ });
+
+ //get the currently assigned property type aliases - ensure we pass these to the server side filer
+ var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function(g) {
+ return _.map(g.properties, function(p) {
+ return p.alias;
+ });
+ })), function (f) {
+ return f !== null && f !== undefined;
+ });
+
+ //use a different resource lookup depending on the content type type
+ var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes;
+
+ return resourceLookup(scope.model.id, selectedContentTypeAliases, propAliasesExisting).then(function (filteredAvailableCompositeTypes) {
+ _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (current) {
+ //reset first
+ current.allowed = true;
+ //see if this list item is found in the response (allowed) list
+ var found = _.find(filteredAvailableCompositeTypes, function (f) {
+ return current.contentType.alias === f.contentType.alias;
+ });
+
+ //allow if the item was found in the response (allowed) list -
+ // and ensure its set to allowed if it is currently checked,
+ // DO not allow if it's a locked content type.
+ current.allowed = scope.model.lockedCompositeContentTypes.indexOf(current.contentType.alias) === -1 &&
+ (selectedContentTypeAliases.indexOf(current.contentType.alias) !== -1) || ((found !== null && found !== undefined) ? found.allowed : false);
+
+ });
+ });
+ }
+
+ function updatePropertiesSortOrder() {
+
+ angular.forEach(scope.model.groups, function(group){
+ if( group.tabState !== "init" ) {
+ group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties);
+ }
+ });
+
+ }
+
+ function setupAvailableContentTypesModel(result) {
+ scope.compositionsDialogModel.availableCompositeContentTypes = result;
+ //iterate each one and set it up
+ _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (c) {
+ //enable it if it's part of the selected model
+ if (scope.compositionsDialogModel.compositeContentTypes.indexOf(c.contentType.alias) !== -1) {
+ c.allowed = true;
+ }
+
+ //set the inherited flags
+ c.inherited = false;
+ if (scope.model.lockedCompositeContentTypes.indexOf(c.contentType.alias) > -1) {
+ c.inherited = true;
+ }
+ // convert icons for composite content types
+ iconHelper.formatContentTypeIcons([c.contentType]);
+ });
+ }
+
+ /* ---------- DELETE PROMT ---------- */
+
+ scope.togglePrompt = function (object) {
+ object.deletePrompt = !object.deletePrompt;
+ };
+
+ scope.hidePrompt = function (object) {
+ object.deletePrompt = false;
+ };
+
+ /* ---------- TOOLBAR ---------- */
+
+ scope.toggleSortingMode = function(tool) {
+
+ if (scope.sortingMode === true) {
+
+ var sortOrderMissing = false;
+
+ for (var i = 0; i < scope.model.groups.length; i++) {
+ var group = scope.model.groups[i];
+ if (group.tabState !== "init" && group.sortOrder === undefined) {
+ sortOrderMissing = true;
+ group.showSortOrderMissing = true;
+ notificationsService.error(validationTranslated + ": " + group.name + " " + tabNoSortOrderTranslated);
+ }
+ }
+
+ if (!sortOrderMissing) {
+ scope.sortingMode = false;
+ scope.sortingButtonKey = "general_reorder";
+ }
+
+ } else {
+
+ scope.sortingMode = true;
+ scope.sortingButtonKey = "general_reorderDone";
+
+ }
+
+ };
+
+ scope.openCompositionsDialog = function() {
+
+ scope.compositionsDialogModel = {
+ title: "Compositions",
+ contentType: scope.model,
+ compositeContentTypes: scope.model.compositeContentTypes,
+ view: "views/common/overlays/contenttypeeditor/compositions/compositions.html",
+ confirmSubmit: {
+ title: "Warning",
+ description: "Removing a composition will delete all the associated property data. Once you save the document type there's no way back, are you sure?",
+ checkboxLabel: "I know what I'm doing",
+ enable: true
+ },
+ submit: function(model, oldModel, confirmed) {
+
+ var compositionRemoved = false;
+
+ // check if any compositions has been removed
+ for(var i = 0; oldModel.compositeContentTypes.length > i; i++) {
+
+ var oldComposition = oldModel.compositeContentTypes[i];
+
+ if(_.contains(model.compositeContentTypes, oldComposition) === false) {
+ compositionRemoved = true;
+ }
+
+ }
+
+ // show overlay confirm box if compositions has been removed.
+ if(compositionRemoved && confirmed === false) {
+
+ scope.compositionsDialogModel.confirmSubmit.show = true;
+
+ // submit overlay if no compositions has been removed
+ // or the action has been confirmed
+ } else {
+
+ // make sure that all tabs has an init property
+ if (scope.model.groups.length !== 0) {
+ angular.forEach(scope.model.groups, function(group) {
+ addInitProperty(group);
+ });
+ }
+
+ // remove overlay
+ scope.compositionsDialogModel.show = false;
+ scope.compositionsDialogModel = null;
+ }
+
+ },
+ close: function(oldModel) {
+
+ // reset composition changes
+ scope.model.groups = oldModel.contentType.groups;
+ scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes;
+
+ // remove overlay
+ scope.compositionsDialogModel.show = false;
+ scope.compositionsDialogModel = null;
+
+ },
+ selectCompositeContentType: function (selectedContentType) {
+
+ //first check if this is a new selection - we need to store this value here before any further digests/async
+ // because after that the scope.model.compositeContentTypes will be populated with the selected value.
+ var newSelection = scope.model.compositeContentTypes.indexOf(selectedContentType.alias) === -1;
+
+ if (newSelection) {
+ //merge composition with content type
+
+ //use a different resource lookup depending on the content type type
+ var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getById : mediaTypeResource.getById;
+
+ resourceLookup(selectedContentType.id).then(function (composition) {
+ //based on the above filtering we shouldn't be able to select an invalid one, but let's be safe and
+ // double check here.
+ var overlappingAliases = contentTypeHelper.validateAddingComposition(scope.model, composition);
+ if (overlappingAliases.length > 0) {
+ //this will create an invalid composition, need to uncheck it
+ scope.compositionsDialogModel.compositeContentTypes.splice(
+ scope.compositionsDialogModel.compositeContentTypes.indexOf(composition.alias), 1);
+ //dissallow this until something else is unchecked
+ selectedContentType.allowed = false;
+ }
+ else {
+ contentTypeHelper.mergeCompositeContentType(scope.model, composition);
+ }
+
+ //based on the selection, we need to filter the available composite types list
+ filterAvailableCompositions(selectedContentType, newSelection).then(function () {
+ //TODO: Here we could probably re-enable selection if we previously showed a throbber or something
+ });
+ });
+ }
+ else {
+ // split composition from content type
+ contentTypeHelper.splitCompositeContentType(scope.model, selectedContentType);
+
+ //based on the selection, we need to filter the available composite types list
+ filterAvailableCompositions(selectedContentType, newSelection).then(function () {
+ //TODO: Here we could probably re-enable selection if we previously showed a throbber or something
+ });
+ }
+
+ }
+ };
+
+ var availableContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes;
+ var countContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getCount : mediaTypeResource.getCount;
+
+ //get the currently assigned property type aliases - ensure we pass these to the server side filer
+ var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function(g) {
+ return _.map(g.properties, function(p) {
+ return p.alias;
+ });
+ })), function(f) {
+ return f !== null && f !== undefined;
+ });
+ $q.all([
+ //get available composite types
+ availableContentTypeResource(scope.model.id, [], propAliasesExisting).then(function (result) {
+ setupAvailableContentTypesModel(result);
+ }),
+ //get content type count
+ countContentTypeResource().then(function(result) {
+ scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10);
+ })
+ ]).then(function() {
+ //resolves when both other promises are done, now show it
+ scope.compositionsDialogModel.show = true;
+ });
+
+ };
+
+
+ /* ---------- GROUPS ---------- */
+
+ scope.addGroup = function(group) {
+
+ // set group sort order
+ var index = scope.model.groups.indexOf(group);
+ var prevGroup = scope.model.groups[index - 1];
+
+ if( index > 0) {
+ // set index to 1 higher than the previous groups sort order
+ group.sortOrder = prevGroup.sortOrder + 1;
+
+ } else {
+ // first group - sort order will be 0
+ group.sortOrder = 0;
+ }
+
+ // activate group
+ scope.activateGroup(group);
+
+ };
+
+ scope.activateGroup = function(selectedGroup) {
+
+ // set all other groups that are inactive to active
+ angular.forEach(scope.model.groups, function(group) {
+ // skip init tab
+ if (group.tabState !== "init") {
+ group.tabState = "inActive";
+ }
+ });
+
+ selectedGroup.tabState = "active";
+
+ };
+
+ scope.removeGroup = function(groupIndex) {
+ scope.model.groups.splice(groupIndex, 1);
+ addInitGroup(scope.model.groups);
+ };
+
+ scope.updateGroupTitle = function(group) {
+ if (group.properties.length === 0) {
+ addInitProperty(group);
+ }
+ };
+
+ scope.changeSortOrderValue = function(group) {
+
+ if (group.sortOrder !== undefined) {
+ group.showSortOrderMissing = false;
+ }
+ scope.model.groups = $filter('orderBy')(scope.model.groups, 'sortOrder');
+ };
+
+ function addInitGroup(groups) {
+
+ // check i init tab already exists
+ var addGroup = true;
+
+ angular.forEach(groups, function(group) {
+ if (group.tabState === "init") {
+ addGroup = false;
+ }
+ });
+
+ if (addGroup) {
+ groups.push({
+ properties: [],
+ parentTabContentTypes: [],
+ parentTabContentTypeNames: [],
+ name: "",
+ tabState: "init"
+ });
+ }
+
+ return groups;
+ }
+
+ function activateFirstGroup(groups) {
+ if (groups && groups.length > 0) {
+ var firstGroup = groups[0];
+ if(!firstGroup.tabState || firstGroup.tabState === "inActive") {
+ firstGroup.tabState = "active";
+ }
+ }
+ }
+
+ /* ---------- PROPERTIES ---------- */
+
+ scope.addProperty = function(property, group) {
+
+ // set property sort order
+ var index = group.properties.indexOf(property);
+ var prevProperty = group.properties[index - 1];
+
+ if( index > 0) {
+ // set index to 1 higher than the previous property sort order
+ property.sortOrder = prevProperty.sortOrder + 1;
+
+ } else {
+ // first property - sort order will be 0
+ property.sortOrder = 0;
+ }
+
+ // open property settings dialog
+ scope.editPropertyTypeSettings(property, group);
+
+ };
+
+ scope.editPropertyTypeSettings = function(property, group) {
+
+ if (!property.inherited && !property.locked) {
+
+ scope.propertySettingsDialogModel = {};
+ scope.propertySettingsDialogModel.title = "Property settings";
+ scope.propertySettingsDialogModel.property = property;
+ scope.propertySettingsDialogModel.contentType = scope.contentType;
+ scope.propertySettingsDialogModel.contentTypeName = scope.model.name;
+ scope.propertySettingsDialogModel.view = "views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html";
+ scope.propertySettingsDialogModel.show = true;
+
+ // set state to active to access the preview
+ property.propertyState = "active";
+
+ // set property states
+ property.dialogIsOpen = true;
+
+ scope.propertySettingsDialogModel.submit = function(model) {
+
+ property.inherited = false;
+ property.dialogIsOpen = false;
+
+ // update existing data types
+ if(model.updateSameDataTypes) {
+ updateSameDataTypes(property);
+ }
+
+ // remove dialog
+ scope.propertySettingsDialogModel.show = false;
+ scope.propertySettingsDialogModel = null;
+
+ // push new init property to group
+ addInitProperty(group);
+
+ // set focus on init property
+ var numberOfProperties = group.properties.length;
+ group.properties[numberOfProperties - 1].focus = true;
+
+ // push new init tab to the scope
+ addInitGroup(scope.model.groups);
+
+ };
+
+ scope.propertySettingsDialogModel.close = function(oldModel) {
+
+ // reset all property changes
+ property.label = oldModel.property.label;
+ property.alias = oldModel.property.alias;
+ property.description = oldModel.property.description;
+ property.config = oldModel.property.config;
+ property.editor = oldModel.property.editor;
+ property.view = oldModel.property.view;
+ property.dataTypeId = oldModel.property.dataTypeId;
+ property.dataTypeIcon = oldModel.property.dataTypeIcon;
+ property.dataTypeName = oldModel.property.dataTypeName;
+ property.validation.mandatory = oldModel.property.validation.mandatory;
+ property.validation.pattern = oldModel.property.validation.pattern;
+ property.showOnMemberProfile = oldModel.property.showOnMemberProfile;
+ property.memberCanEdit = oldModel.property.memberCanEdit;
+
+ // because we set state to active, to show a preview, we have to check if has been filled out
+ // label is required so if it is not filled we know it is a placeholder
+ if(oldModel.property.editor === undefined || oldModel.property.editor === null || oldModel.property.editor === "") {
+ property.propertyState = "init";
+ } else {
+ property.propertyState = oldModel.property.propertyState;
+ }
+
+ // remove dialog
+ scope.propertySettingsDialogModel.show = false;
+ scope.propertySettingsDialogModel = null;
+
+ };
+
+ }
+ };
+
+ scope.deleteProperty = function(tab, propertyIndex) {
+
+ // remove property
+ tab.properties.splice(propertyIndex, 1);
+
+ // if the last property in group is an placeholder - remove add new tab placeholder
+ if(tab.properties.length === 1 && tab.properties[0].propertyState === "init") {
+
+ angular.forEach(scope.model.groups, function(group, index, groups){
+ if(group.tabState === 'init') {
+ groups.splice(index, 1);
+ }
+ });
+
+ }
+
+ };
+
+ function addInitProperty(group) {
+
+ var addInitPropertyBool = true;
+ var initProperty = {
+ label: null,
+ alias: null,
+ propertyState: "init",
+ validation: {
+ mandatory: false,
+ pattern: null
+ }
+ };
+
+ // check if there already is an init property
+ angular.forEach(group.properties, function(property) {
+ if (property.propertyState === "init") {
+ addInitPropertyBool = false;
+ }
+ });
+
+ if (addInitPropertyBool) {
+ group.properties.push(initProperty);
+ }
+
+ return group;
+ }
+
+ function updateSameDataTypes(newProperty) {
+
+ // find each property
+ angular.forEach(scope.model.groups, function(group){
+ angular.forEach(group.properties, function(property){
+
+ if(property.dataTypeId === newProperty.dataTypeId) {
+
+ // update property data
+ property.config = newProperty.config;
+ property.editor = newProperty.editor;
+ property.view = newProperty.view;
+ property.dataTypeId = newProperty.dataTypeId;
+ property.dataTypeIcon = newProperty.dataTypeIcon;
+ property.dataTypeName = newProperty.dataTypeName;
+
+ }
+
+ });
+ });
+ }
+
+
+ var unbindModelWatcher = scope.$watch('model', function(newValue, oldValue) {
+ if (newValue !== undefined && newValue.groups !== undefined) {
+ activate();
+ }
+ });
+
+ // clean up
+ scope.$on('$destroy', function(){
+ unbindModelWatcher();
+ });
+
+ }
+
+ var directive = {
+ restrict: "E",
+ replace: true,
+ templateUrl: "views/components/umb-groups-builder.html",
+ scope: {
+ model: "=",
+ compositions: "=",
+ sorting: "=",
+ contentType: "@"
+ },
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbGroupsBuilder', GroupsBuilderDirective);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbkeyboardShortcutsOverview
+@restrict E
+@scope
+
+@description
+
+
Use this directive to show an overview of keyboard shortcuts in an editor.
+The directive will render an overview trigger wich shows how the overview is opened.
+When this combination is hit an overview is opened with shortcuts based on the model sent to the directive.
+**/
+
+(function() {
+ 'use strict';
+
+ function UmbLoadIndicatorDirective() {
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-load-indicator.html'
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbLoadIndicator', UmbLoadIndicatorDirective);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbLockedField
+@restrict E
+@scope
+
+@description
+Use this directive to render a value with a lock next to it. When the lock is clicked the value gets unlocked and can be edited.
+
+
Markup example
+
+
+
+
+
+
+
+
+
+
Controller example
+
+ (function () {
+ "use strict";
+
+ function Controller() {
+
+ var vm = this;
+ vm.value = "My locked text";
+
+ }
+
+ angular.module("umbraco").controller("My.Controller", Controller);
+
+ })();
+
+
+@param {string} ngModel (binding): The locked text.
+@param {boolean=} locked (binding): true by default. Set to false to unlock the text.
+@param {string=} placeholderText (binding): If ngModel is empty this text will be shown.
+@param {string=} regexValidation (binding): Set a regex expression for validation of the field.
+@param {string=} serverValidationField (attribute): Set a server validation field.
+**/
+
+(function() {
+ 'use strict';
+
+ function LockedFieldDirective($timeout, localizationService) {
+
+ function link(scope, el, attr, ngModelCtrl) {
+
+ function activate() {
+
+ // if locked state is not defined as an attr set default state
+ if (scope.locked === undefined || scope.locked === null) {
+ scope.locked = true;
+ }
+
+ // if regex validation is not defined as an attr set default state
+ // if this is set to an empty string then regex validation can be ignored.
+ if (scope.regexValidation === undefined || scope.regexValidation === null) {
+ scope.regexValidation = "^[a-zA-Z]\\w.*$";
+ }
+
+ if (scope.serverValidationField === undefined || scope.serverValidationField === null) {
+ scope.serverValidationField = "";
+ }
+
+ // if locked state is not defined as an attr set default state
+ if (scope.placeholderText === undefined || scope.placeholderText === null) {
+ scope.placeholderText = "Enter value...";
+ }
+
+ }
+
+ scope.lock = function() {
+ scope.locked = true;
+ };
+
+ scope.unlock = function() {
+ scope.locked = false;
+ };
+
+ activate();
+
+ }
+
+ var directive = {
+ require: "ngModel",
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-locked-field.html',
+ scope: {
+ ngModel: "=",
+ locked: "=?",
+ placeholderText: "=?",
+ regexValidation: "=?",
+ serverValidationField: "@"
+ },
+ link: link
+ };
+
+ return directive;
+
+ }
+
+ angular.module('umbraco.directives').directive('umbLockedField', LockedFieldDirective);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbMediaGrid
+@restrict E
+@scope
+
+@description
+Use this directive to generate a thumbnail grid of media items.
+
+
Markup example
+
+
+
+
+
+
+
+
+
+
Controller example
+
+ (function () {
+ "use strict";
+
+ function Controller() {
+
+ var vm = this;
+ vm.mediaItems = [];
+
+ vm.clickItem = clickItem;
+ vm.clickItemName = clickItemName;
+
+ myService.getMediaItems().then(function (mediaItems) {
+ vm.mediaItems = mediaItems;
+ });
+
+ function clickItem(item, $event, $index){
+ // do magic here
+ }
+
+ function clickItemName(item, $event, $index) {
+ // set item.selected = true; to select the item
+ // do magic here
+ }
+
+ }
+
+ angular.module("umbraco").controller("My.Controller", Controller);
+ })();
+
+
+@param {array} items (binding): Array of media items.
+@param {callback=} onDetailsHover (binding): Callback method when the details icon is hovered.
+
The callback returns:
+
+
item: The hovered item
+
$event: The hover event
+
hover: Boolean to tell if the item is hovered or not
+
+@param {callback=} onClick (binding): Callback method to handle click events on the media item.
+
The callback returns:
+
+
item: The clicked item
+
$event: The click event
+
$index: The item index
+
+@param {callback=} onClickName (binding): Callback method to handle click events on the media item name.
+
The callback returns:
+
+
item: The clicked item
+
$event: The click event
+
$index: The item index
+
+@param {string=} filterBy (binding): String to filter media items by
+@param {string=} itemMaxWidth (attribute): Sets a max width on the media item thumbnails.
+@param {string=} itemMaxHeight (attribute): Sets a max height on the media item thumbnails.
+@param {string=} itemMinWidth (attribute): Sets a min width on the media item thumbnails.
+@param {string=} itemMinHeight (attribute): Sets a min height on the media item thumbnails.
+
+**/
+
+(function() {
+ 'use strict';
+
+ function MediaGridDirective($filter, mediaHelper) {
+
+ function link(scope, el, attr, ctrl) {
+
+ var itemDefaultHeight = 200;
+ var itemDefaultWidth = 200;
+ var itemMaxWidth = 200;
+ var itemMaxHeight = 200;
+ var itemMinWidth = 125;
+ var itemMinHeight = 125;
+
+ function activate() {
+
+ if (scope.itemMaxWidth) {
+ itemMaxWidth = scope.itemMaxWidth;
+ }
+
+ if (scope.itemMaxHeight) {
+ itemMaxHeight = scope.itemMaxHeight;
+ }
+
+ if (scope.itemMinWidth) {
+ itemMinWidth = scope.itemMinWidth;
+ }
+
+ if (scope.itemMinWidth) {
+ itemMinHeight = scope.itemMinHeight;
+ }
+
+ for (var i = 0; scope.items.length > i; i++) {
+ var item = scope.items[i];
+ setItemData(item);
+ setOriginalSize(item, itemMaxHeight);
+
+ // remove non images when onlyImages is set to true
+ if(scope.onlyImages === "true" && !item.isFolder && !item.thumbnail){
+ scope.items.splice(i, 1);
+ i--;
+ }
+
+ }
+
+ if (scope.items.length > 0) {
+ setFlexValues(scope.items);
+ }
+
+ }
+
+ function setItemData(item) {
+ item.isFolder = !mediaHelper.hasFilePropertyType(item);
+ if (!item.isFolder) {
+ item.thumbnail = mediaHelper.resolveFile(item, true);
+ item.image = mediaHelper.resolveFile(item, false);
+
+ var fileProp = _.find(item.properties, function (v) {
+ return (v.alias === "umbracoFile");
+ });
+
+ if (fileProp && fileProp.value) {
+ item.file = fileProp.value;
+ }
+
+ var extensionProp = _.find(item.properties, function (v) {
+ return (v.alias === "umbracoExtension");
+ });
+
+ if (extensionProp && extensionProp.value) {
+ item.extension = extensionProp.value;
+ }
+ }
+ }
+
+ function setOriginalSize(item, maxHeight) {
+
+ //set to a square by default
+ item.width = itemDefaultWidth;
+ item.height = itemDefaultHeight;
+ item.aspectRatio = 1;
+
+ var widthProp = _.find(item.properties, function(v) {
+ return (v.alias === "umbracoWidth");
+ });
+
+ if (widthProp && widthProp.value) {
+ item.width = parseInt(widthProp.value, 10);
+ if (isNaN(item.width)) {
+ item.width = itemDefaultWidth;
+ }
+ }
+
+ var heightProp = _.find(item.properties, function(v) {
+ return (v.alias === "umbracoHeight");
+ });
+
+ if (heightProp && heightProp.value) {
+ item.height = parseInt(heightProp.value, 10);
+ if (isNaN(item.height)) {
+ item.height = itemDefaultWidth;
+ }
+ }
+
+ item.aspectRatio = item.width / item.height;
+
+ // set max width and height
+ // landscape
+ if (item.aspectRatio >= 1) {
+ if (item.width > itemMaxWidth) {
+ item.width = itemMaxWidth;
+ item.height = itemMaxWidth / item.aspectRatio;
+ }
+ // portrait
+ } else {
+ if (item.height > itemMaxHeight) {
+ item.height = itemMaxHeight;
+ item.width = itemMaxHeight * item.aspectRatio;
+ }
+ }
+
+ }
+
+ function setFlexValues(mediaItems) {
+
+ var flexSortArray = mediaItems;
+ var smallestImageWidth = null;
+ var widestImageAspectRatio = null;
+
+ // sort array after image width with the widest image first
+ flexSortArray = $filter('orderBy')(flexSortArray, 'width', true);
+
+ // find widest image aspect ratio
+ widestImageAspectRatio = flexSortArray[0].aspectRatio;
+
+ // find smallest image width
+ smallestImageWidth = flexSortArray[flexSortArray.length - 1].width;
+
+ for (var i = 0; flexSortArray.length > i; i++) {
+
+ var mediaItem = flexSortArray[i];
+ var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio);
+
+ if (flex === 0) {
+ flex = 1;
+ }
+
+ var imageMinFlexWidth = smallestImageWidth * flex;
+
+ var flexStyle = {
+ "flex": flex + " 1 " + imageMinFlexWidth + "px",
+ "max-width": mediaItem.width + "px",
+ "min-width": itemMinWidth + "px",
+ "min-height": itemMinHeight + "px"
+ };
+
+ mediaItem.flexStyle = flexStyle;
+
+ }
+
+ }
+
+ scope.clickItem = function(item, $event, $index) {
+ if (scope.onClick) {
+ scope.onClick(item, $event, $index);
+ }
+ };
+
+ scope.clickItemName = function(item, $event, $index) {
+ if (scope.onClickName) {
+ scope.onClickName(item, $event, $index);
+ $event.stopPropagation();
+ }
+ };
+
+ scope.hoverItemDetails = function(item, $event, hover) {
+ if (scope.onDetailsHover) {
+ scope.onDetailsHover(item, $event, hover);
+ }
+ };
+
+ var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue) {
+ if (angular.isArray(newValue)) {
+ activate();
+ }
+ });
+
+ scope.$on('$destroy', function() {
+ unbindItemsWatcher();
+ });
+
+ }
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-media-grid.html',
+ scope: {
+ items: '=',
+ onDetailsHover: "=",
+ onClick: '=',
+ onClickName: "=",
+ filterBy: "=",
+ itemMaxWidth: "@",
+ itemMaxHeight: "@",
+ itemMinWidth: "@",
+ itemMinHeight: "@",
+ onlyImages: "@"
+ },
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbPagination
+@restrict E
+@scope
+
+@description
+Use this directive to generate a pagination.
+
+
Markup example
+
+
+
+
+
+
+
+
+
+
Controller example
+
+ (function () {
+ "use strict";
+
+ function Controller() {
+
+ var vm = this;
+
+ vm.pagination = {
+ pageNumber: 1,
+ totalPages: 10
+ }
+
+ vm.nextPage = nextPage;
+ vm.prevPage = prevPage;
+ vm.goToPage = goToPage;
+
+ function nextPage(pageNumber) {
+ // do magic here
+ console.log(pageNumber);
+ alert("nextpage");
+ }
+
+ function prevPage(pageNumber) {
+ // do magic here
+ console.log(pageNumber);
+ alert("prevpage");
+ }
+
+ function goToPage(pageNumber) {
+ // do magic here
+ console.log(pageNumber);
+ alert("go to");
+ }
+
+ }
+
+ angular.module("umbraco").controller("My.Controller", Controller);
+ })();
+
+
+@param {number} pageNumber (binding): Current page number.
+@param {number} totalPages (binding): The total number of pages.
+@param {callback} onNext (binding): Callback method to go to the next page.
+
The callback returns:
+
+
pageNumber: The page number
+
+@param {callback=} onPrev (binding): Callback method to go to the previous page.
+
The callback returns:
+
+
pageNumber: The page number
+
+@param {callback=} onGoToPage (binding): Callback method to go to a specific page.
+
The callback returns:
+
+
pageNumber: The page number
+
+**/
+
+(function() {
+ 'use strict';
+
+ function PaginationDirective() {
+
+ function link(scope, el, attr, ctrl) {
+
+ function activate() {
+
+ scope.pagination = [];
+
+ var i = 0;
+
+ if (scope.totalPages <= 10) {
+ for (i = 0; i < scope.totalPages; i++) {
+ scope.pagination.push({
+ val: (i + 1),
+ isActive: scope.pageNumber === (i + 1)
+ });
+ }
+ }
+ else {
+ //if there is more than 10 pages, we need to do some fancy bits
+
+ //get the max index to start
+ var maxIndex = scope.totalPages - 10;
+ //set the start, but it can't be below zero
+ var start = Math.max(scope.pageNumber - 5, 0);
+ //ensure that it's not too far either
+ start = Math.min(maxIndex, start);
+
+ for (i = start; i < (10 + start) ; i++) {
+ scope.pagination.push({
+ val: (i + 1),
+ isActive: scope.pageNumber === (i + 1)
+ });
+ }
+
+ //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing
+ if (start > 0) {
+ scope.pagination.unshift({ name: "First", val: 1, isActive: false }, {val: "...",isActive: false});
+ }
+
+ //same for the end
+ if (start < maxIndex) {
+ scope.pagination.push({ val: "...", isActive: false }, { name: "Last", val: scope.totalPages, isActive: false });
+ }
+ }
+
+ }
+
+ scope.next = function() {
+ if (scope.onNext && scope.pageNumber < scope.totalPages) {
+ scope.pageNumber++;
+ scope.onNext(scope.pageNumber);
+ }
+ };
+
+ scope.prev = function(pageNumber) {
+ if (scope.onPrev && scope.pageNumber > 1) {
+ scope.pageNumber--;
+ scope.onPrev(scope.pageNumber);
+ }
+ };
+
+ scope.goToPage = function(pageNumber) {
+ if(scope.onGoToPage) {
+ scope.pageNumber = pageNumber + 1;
+ scope.onGoToPage(scope.pageNumber);
+ }
+ };
+
+ var unbindPageNumberWatcher = scope.$watch('pageNumber', function(newValue, oldValue){
+ activate();
+ });
+
+ scope.$on('$destroy', function(){
+ unbindPageNumberWatcher();
+ });
+
+ activate();
+
+ }
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-pagination.html',
+ scope: {
+ pageNumber: "=",
+ totalPages: "=",
+ onNext: "=",
+ onPrev: "=",
+ onGoToPage: "="
+ },
+ link: link
+ };
+
+ return directive;
+
+ }
+
+ angular.module('umbraco.directives').directive('umbPagination', PaginationDirective);
+
+})();
+
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbProgressBar
+@restrict E
+@scope
+
+@description
+Use this directive to generate a progress bar.
+
+
Markup example
+
+
+
+
+
+@param {number} percentage (attribute): The progress in percentage.
+**/
+
+(function() {
+ 'use strict';
+
+ function ProgressBarDirective() {
+
+ var directive = {
+ restrict: 'E',
+ replace: true,
+ templateUrl: 'views/components/umb-progress-bar.html',
+ scope: {
+ percentage: "@"
+ }
+ };
+
+ return directive;
+
+ }
+
+ angular.module('umbraco.directives').directive('umbProgressBar', ProgressBarDirective);
+
+})();
+
+/**
+@ngdoc directive
+@name umbraco.directives.directive:umbStickyBar
+@restrict A
+
+@description
+Use this directive make an element sticky and follow the page when scrolling.
+
+
+
+@param {string} event Set the $event from the target element to position the tooltip relative to the mouse cursor.
+**/
+
+(function() {
+ 'use strict';
+
+ function TooltipDirective($timeout) {
+
+ function link(scope, el, attr, ctrl) {
+
+ scope.tooltipStyles = {};
+ scope.tooltipStyles.left = 0;
+ scope.tooltipStyles.top = 0;
+
+ function activate() {
+
+ $timeout(function() {
+ setTooltipPosition(scope.event);
+ });
+
+ }
+
+ function setTooltipPosition(event) {
+
+ var container = $("#contentwrapper");
+ var containerLeft = container[0].offsetLeft;
+ var containerRight = containerLeft + container[0].offsetWidth;
+ var containerTop = container[0].offsetTop;
+ var containerBottom = containerTop + container[0].offsetHeight;
+
+ var elementHeight = null;
+ var elementWidth = null;
+
+ var position = {
+ right: "inherit",
+ left: "inherit",
+ top: "inherit",
+ bottom: "inherit"
+ };
+
+ // element size
+ elementHeight = el.context.clientHeight;
+ elementWidth = el.context.clientWidth;
+
+ position.left = event.pageX - (elementWidth / 2);
+ position.top = event.pageY;
+
+ // check to see if element is outside screen
+ // outside right
+ if (position.left + elementWidth > containerRight) {
+ position.right = 10;
+ position.left = "inherit";
+ }
+
+ // outside bottom
+ if (position.top + elementHeight > containerBottom) {
+ position.bottom = 10;
+ position.top = "inherit";
+ }
+
+ // outside left
+ if (position.left < containerLeft) {
+ position.left = containerLeft + 10;
+ position.right = "inherit";
+ }
+
+ // outside top
+ if (position.top < containerTop) {
+ position.top = 10;
+ position.bottom = "inherit";
+ }
+
+ scope.tooltipStyles = position;
+
+ el.css(position);
+
+ }
+
+ activate();
+
+ }
+
+ var directive = {
+ restrict: 'E',
+ transclude: true,
+ replace: true,
+ templateUrl: 'views/components/umb-tooltip.html',
+ scope: {
+ event: "="
+ },
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbTooltip', TooltipDirective);
+
+})();
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbFileDropzone
+* @restrict E
+* @function
+* @description
+* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
+**/
+
+/*
+TODO
+.directive("umbFileDrop", function ($timeout, $upload, localizationService, umbRequestHelper){
+
+ return{
+ restrict: "A",
+ link: function(scope, element, attrs){
+
+ //load in the options model
+
+
+ }
+ }
+})
+*/
+
+angular.module("umbraco.directives")
+
+.directive('umbFileDropzone', function ($timeout, Upload, localizationService, umbRequestHelper) {
+ return {
+
+ restrict: 'E',
+ replace: true,
+
+ templateUrl: 'views/components/upload/umb-file-dropzone.html',
+
+ scope: {
+ parentId: '@',
+ contentTypeAlias: '@',
+ propertyAlias: '@',
+ accept: '@',
+ maxFileSize: '@',
+
+ compact: '@',
+ hideDropzone: '@',
+
+ filesQueued: '=',
+ handleFile: '=',
+ filesUploaded: '='
+ },
+
+ link: function(scope, element, attrs) {
+
+ scope.queue = [];
+ scope.done = [];
+ scope.rejected = [];
+ scope.currentFile = undefined;
+
+ function _filterFile(file) {
+
+ var ignoreFileNames = ['Thumbs.db'];
+ var ignoreFileTypes = ['directory'];
+
+ // ignore files with names from the list
+ // ignore files with types from the list
+ // ignore files which starts with "."
+ if(ignoreFileNames.indexOf(file.name) === -1 &&
+ ignoreFileTypes.indexOf(file.type) === -1 &&
+ file.name.indexOf(".") !== 0) {
+ return true;
+ } else {
+ return false;
+ }
+
+ }
+
+ function _filesQueued(files, event){
+
+ //Push into the queue
+ angular.forEach(files, function(file){
+
+ if(_filterFile(file) === true) {
+
+ if(file.$error) {
+ scope.rejected.push(file);
+ } else {
+ scope.queue.push(file);
+ }
+
+ }
+
+ });
+
+ //when queue is done, kick the uploader
+ if(!scope.working){
+ _processQueueItem();
+ }
+ }
+
+
+ function _processQueueItem(){
+
+ if(scope.queue.length > 0){
+ scope.currentFile = scope.queue.shift();
+ _upload(scope.currentFile);
+ }else if(scope.done.length > 0){
+
+ if(scope.filesUploaded){
+ //queue is empty, trigger the done action
+ scope.filesUploaded(scope.done);
+ }
+
+ //auto-clear the done queue after 3 secs
+ var currentLength = scope.done.length;
+ $timeout(function(){
+ scope.done.splice(0, currentLength);
+ }, 3000);
+ }
+ }
+
+ function _upload(file) {
+
+ scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : "umbracoFile";
+ scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : "Image";
+
+ Upload.upload({
+ url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile"),
+ fields: {
+ 'currentFolder': scope.parentId,
+ 'contentTypeAlias': scope.contentTypeAlias,
+ 'propertyAlias': scope.propertyAlias,
+ 'path': file.path
+ },
+ file: file
+ }).progress(function (evt) {
+
+ // calculate progress in percentage
+ var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10);
+
+ // set percentage property on file
+ file.uploadProgress = progressPercentage;
+
+ // set uploading status on file
+ file.uploadStatus = "uploading";
+
+ }).success(function (data, status, headers, config) {
+
+ if(data.notifications && data.notifications.length > 0) {
+
+ // set error status on file
+ file.uploadStatus = "error";
+
+ // Throw message back to user with the cause of the error
+ file.serverErrorMessage = data.notifications[0].message;
+
+ // Put the file in the rejected pool
+ scope.rejected.push(file);
+
+ } else {
+
+ // set done status on file
+ file.uploadStatus = "done";
+
+ // set date/time for when done - used for sorting
+ file.doneDate = new Date();
+
+ // Put the file in the done pool
+ scope.done.push(file);
+
+ }
+
+ scope.currentFile = undefined;
+
+ //after processing, test if everthing is done
+ _processQueueItem();
+
+ }).error( function (evt, status, headers, config) {
+
+ // set status done
+ file.uploadStatus = "error";
+
+ //if the service returns a detailed error
+ if (evt.InnerException) {
+ file.serverErrorMessage = evt.InnerException.ExceptionMessage;
+
+ //Check if its the common "too large file" exception
+ if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) {
+ file.serverErrorMessage = "File too large to upload";
+ }
+
+ } else if (evt.Message) {
+ file.serverErrorMessage = evt.Message;
+ }
+
+ // If file not found, server will return a 404 and display this message
+ if(status === 404 ) {
+ file.serverErrorMessage = "File not found";
+ }
+
+ //after processing, test if everthing is done
+ scope.rejected.push(file);
+ scope.currentFile = undefined;
+
+ _processQueueItem();
+ });
+ }
+
+
+ scope.handleFiles = function(files, event){
+ if(scope.filesQueued){
+ scope.filesQueued(files, event);
+ }
+
+ _filesQueued(files, event);
+
+ };
+
+ }
+
+
+ };
+ });
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbFileUpload
+* @function
+* @restrict A
+* @scope
+* @description
+* Listens for file input control changes and emits events when files are selected for use in other controllers.
+**/
+function umbFileUpload() {
+ return {
+ restrict: "A",
+ scope: true, //create a new scope
+ link: function (scope, el, attrs) {
+ el.bind('change', function (event) {
+ var files = event.target.files;
+ //emit event upward
+ scope.$emit("filesSelected", { files: files });
+ });
+ }
+ };
+}
+
+angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload);
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:umbSingleFileUpload
+* @function
+* @restrict A
+* @scope
+* @description
+* A single file upload field that will reset itself based on the object passed in for the rebuild parameter. This
+* is required because the only way to reset an upload control is to replace it's html.
+**/
+function umbSingleFileUpload($compile) {
+ return {
+ restrict: "E",
+ scope: {
+ rebuild: "="
+ },
+ replace: true,
+ template: "",
+ link: function (scope, el, attrs) {
+
+ scope.$watch("rebuild", function (newVal, oldVal) {
+ if (newVal && newVal !== oldVal) {
+ //recompile it!
+ el.html("");
+ $compile(el.contents())(scope);
+ }
+ });
+
+ }
+ };
+}
+
+angular.module('umbraco.directives').directive("umbSingleFileUpload", umbSingleFileUpload);
+/**
+ * Konami Code directive for AngularJS
+ * @version v0.0.1
+ * @license MIT License, http://www.opensource.org/licenses/MIT
+ */
+
+angular.module('umbraco.directives')
+ .directive('konamiCode', ['$document', function ($document) {
+ var konamiKeysDefault = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
+
+ return {
+ restrict: 'A',
+ link: function (scope, element, attr) {
+
+ if (!attr.konamiCode) {
+ throw ('Konami directive must receive an expression as value.');
+ }
+
+ // Let user define a custom code.
+ var konamiKeys = attr.konamiKeys || konamiKeysDefault;
+ var keyIndex = 0;
+
+ /**
+ * Fired when konami code is type.
+ */
+ function activated() {
+ if ('konamiOnce' in attr) {
+ stopListening();
+ }
+ // Execute expression.
+ scope.$eval(attr.konamiCode);
+ }
+
+ /**
+ * Handle keydown events.
+ */
+ function keydown(e) {
+ if (e.keyCode === konamiKeys[keyIndex++]) {
+ if (keyIndex === konamiKeys.length) {
+ keyIndex = 0;
+ activated();
+ }
+ } else {
+ keyIndex = 0;
+ }
+ }
+
+ /**
+ * Stop to listen typing.
+ */
+ function stopListening() {
+ $document.off('keydown', keydown);
+ }
+
+ // Start listening to key typing.
+ $document.on('keydown', keydown);
+
+ // Stop listening when scope is destroyed.
+ scope.$on('$destroy', stopListening);
+ }
+ };
+ }]);
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:noDirtyCheck
+* @restrict A
+* @description Can be attached to form inputs to prevent them from setting the form as dirty (http://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs)
+**/
+function noDirtyCheck() {
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ link: function (scope, elm, attrs, ctrl) {
+ elm.focus(function () {
+ ctrl.$pristine = false;
+ });
+ }
+ };
+}
+angular.module('umbraco.directives.validation').directive("noDirtyCheck", noDirtyCheck);
+(function() {
+ 'use strict';
+
+ function SetDirtyOnChange() {
+
+ function link(scope, el, attr, ctrl) {
+
+ var initValue = attr.umbSetDirtyOnChange;
+
+ attr.$observe("umbSetDirtyOnChange", function (newValue) {
+ if(newValue !== initValue) {
+ ctrl.$setDirty();
+ }
+ });
+
+ }
+
+ var directive = {
+ require: "^form",
+ restrict: 'A',
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('umbSetDirtyOnChange', SetDirtyOnChange);
+
+})();
+
+/**
+ * General-purpose validator for ngModel.
+ * angular.js comes with several built-in validation mechanism for input fields (ngRequired, ngPattern etc.) but using
+ * an arbitrary validation function requires creation of a custom formatters and / or parsers.
+ * The ui-validate directive makes it easy to use any function(s) defined in scope as a validator function(s).
+ * A validator function will trigger validation on both model and input changes.
+ *
+ * @example
+ * @example
+ * @example
+ * @example
+ *
+ * @param val-custom {string|object literal} If strings is passed it should be a scope's function to be used as a validator.
+ * If an object literal is passed a key denotes a validation error key while a value should be a validator function.
+ * In both cases validator function should take a value to validate as its argument and should return true/false indicating a validation result.
+ */
+
+ /*
+ This code comes from the angular UI project, we had to change the directive name and module
+ but other then that its unmodified
+ */
+angular.module('umbraco.directives.validation')
+.directive('valCustom', function () {
+
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ link: function (scope, elm, attrs, ctrl) {
+ var validateFn, watch, validators = {},
+ validateExpr = scope.$eval(attrs.valCustom);
+
+ if (!validateExpr){ return;}
+
+ if (angular.isString(validateExpr)) {
+ validateExpr = { validator: validateExpr };
+ }
+
+ angular.forEach(validateExpr, function (exprssn, key) {
+ validateFn = function (valueToValidate) {
+ var expression = scope.$eval(exprssn, { '$value' : valueToValidate });
+ if (angular.isObject(expression) && angular.isFunction(expression.then)) {
+ // expression is a promise
+ expression.then(function(){
+ ctrl.$setValidity(key, true);
+ }, function(){
+ ctrl.$setValidity(key, false);
+ });
+ return valueToValidate;
+ } else if (expression) {
+ // expression is true
+ ctrl.$setValidity(key, true);
+ return valueToValidate;
+ } else {
+ // expression is false
+ ctrl.$setValidity(key, false);
+ return undefined;
+ }
+ };
+ validators[key] = validateFn;
+
+ ctrl.$parsers.push(validateFn);
+ });
+
+ function apply_watch(watch)
+ {
+ //string - update all validators on expression change
+ if (angular.isString(watch))
+ {
+ scope.$watch(watch, function(){
+ angular.forEach(validators, function(validatorFn){
+ validatorFn(ctrl.$modelValue);
+ });
+ });
+ return;
+ }
+
+ //array - update all validators on change of any expression
+ if (angular.isArray(watch))
+ {
+ angular.forEach(watch, function(expression){
+ scope.$watch(expression, function()
+ {
+ angular.forEach(validators, function(validatorFn){
+ validatorFn(ctrl.$modelValue);
+ });
+ });
+ });
+ return;
+ }
+
+ //object - update appropriate validator
+ if (angular.isObject(watch))
+ {
+ angular.forEach(watch, function(expression, validatorKey)
+ {
+ //value is string - look after one expression
+ if (angular.isString(expression))
+ {
+ scope.$watch(expression, function(){
+ validators[validatorKey](ctrl.$modelValue);
+ });
+ }
+
+ //value is array - look after all expressions in array
+ if (angular.isArray(expression))
+ {
+ angular.forEach(expression, function(intExpression)
+ {
+ scope.$watch(intExpression, function(){
+ validators[validatorKey](ctrl.$modelValue);
+ });
+ });
+ }
+ });
+ }
+ }
+ // Support for val-custom-watch
+ if (attrs.valCustomWatch){
+ apply_watch( scope.$eval(attrs.valCustomWatch) );
+ }
+ }
+ };
+});
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:valHighlight
+* @restrict A
+* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second
+**/
+function valHighlight($timeout) {
+ return {
+ restrict: "A",
+ link: function (scope, element, attrs, ctrl) {
+
+ attrs.$observe("valHighlight", function (newVal) {
+ if (newVal === "true") {
+ element.addClass("highlight-error");
+ $timeout(function () {
+ //set the bound scope property to false
+ scope[attrs.valHighlight] = false;
+ }, 1000);
+ }
+ else {
+ element.removeClass("highlight-error");
+ }
+ });
+
+ }
+ };
+}
+angular.module('umbraco.directives.validation').directive("valHighlight", valHighlight);
+
+angular.module('umbraco.directives.validation')
+ .directive('valCompare',function () {
+ return {
+ require: "ngModel",
+ link: function (scope, elem, attrs, ctrl) {
+
+ //TODO: Pretty sure this should be done using a requires ^form in the directive declaration
+ var otherInput = elem.inheritedData("$formController")[attrs.valCompare];
+
+ ctrl.$parsers.push(function(value) {
+ if(value === otherInput.$viewValue) {
+ ctrl.$setValidity("valCompare", true);
+ return value;
+ }
+ ctrl.$setValidity("valCompare", false);
+ });
+
+ otherInput.$parsers.push(function(value) {
+ ctrl.$setValidity("valCompare", value === ctrl.$viewValue);
+ return value;
+ });
+ }
+ };
+});
+/**
+ * @ngdoc directive
+ * @name umbraco.directives.directive:valEmail
+ * @restrict A
+ * @description A custom directive to validate an email address string, this is required because angular's default validator is incorrect.
+ **/
+function valEmail(valEmailExpression) {
+
+ return {
+ require: 'ngModel',
+ restrict: "A",
+ link: function (scope, elm, attrs, ctrl) {
+
+ var patternValidator = function (viewValue) {
+ //NOTE: we don't validate on empty values, use required validator for that
+ if (!viewValue || valEmailExpression.EMAIL_REGEXP.test(viewValue)) {
+ // it is valid
+ ctrl.$setValidity('valEmail', true);
+ //assign a message to the validator
+ ctrl.errorMsg = "";
+ return viewValue;
+ }
+ else {
+ // it is invalid, return undefined (no model update)
+ ctrl.$setValidity('valEmail', false);
+ //assign a message to the validator
+ ctrl.errorMsg = "Invalid email";
+ return undefined;
+ }
+ };
+
+ //if there is an attribute: type="email" then we need to remove those formatters and parsers
+ if (attrs.type === "email") {
+ //we need to remove the existing parsers = the default angular one which is created by
+ // type="email", but this has a regex issue, so we'll remove that and add our custom one
+ ctrl.$parsers.pop();
+ //we also need to remove the existing formatter - the default angular one will not render
+ // what it thinks is an invalid email address, so it will just be blank
+ ctrl.$formatters.pop();
+ }
+
+ ctrl.$parsers.push(patternValidator);
+ }
+ };
+}
+
+angular.module('umbraco.directives.validation')
+ .directive("valEmail", valEmail)
+ .factory('valEmailExpression', function () {
+ //NOTE: This is the fixed regex which is part of the newer angular
+ return {
+ EMAIL_REGEXP: /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i
+ };
+ });
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:valFormManager
+* @restrict A
+* @require formController
+* @description Used to broadcast an event to all elements inside this one to notify that form validation has
+* changed. If we don't use this that means you have to put a watch for each directive on a form's validation
+* changing which would result in much higher processing. We need to actually watch the whole $error collection of a form
+* because just watching $valid or $invalid doesn't acurrately trigger form validation changing.
+* This also sets the show-validation (or a custom) css class on the element when the form is invalid - this lets
+* us css target elements to be displayed when the form is submitting/submitted.
+* Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will
+* be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly.
+**/
+function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) {
+ return {
+ require: "form",
+ restrict: "A",
+ controller: function($scope) {
+ //This exposes an API for direct use with this directive
+
+ var unsubscribe = [];
+ var self = this;
+
+ //This is basically the same as a directive subscribing to an event but maybe a little
+ // nicer since the other directive can use this directive's API instead of a magical event
+ this.onValidationStatusChanged = function (cb) {
+ unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) {
+ cb.apply(self, [evt, args]);
+ }));
+ };
+
+ //Ensure to remove the event handlers when this instance is destroyted
+ $scope.$on('$destroy', function () {
+ for (var u in unsubscribe) {
+ unsubscribe[u]();
+ }
+ });
+ },
+ link: function (scope, element, attr, formCtrl) {
+
+ scope.$watch(function () {
+ return formCtrl.$error;
+ }, function (e) {
+ scope.$broadcast("valStatusChanged", { form: formCtrl });
+
+ //find all invalid elements' .control-group's and apply the error class
+ var inError = element.find(".control-group .ng-invalid").closest(".control-group");
+ inError.addClass("error");
+
+ //find all control group's that have no error and ensure the class is removed
+ var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError);
+ noInError.removeClass("error");
+
+ }, true);
+
+ var className = attr.valShowValidation ? attr.valShowValidation : "show-validation";
+ var savingEventName = attr.savingEvent ? attr.savingEvent : "formSubmitting";
+ var savedEvent = attr.savedEvent ? attr.savingEvent : "formSubmitted";
+
+ //This tracks if the user is currently saving a new item, we use this to determine
+ // if we should display the warning dialog that they are leaving the page - if a new item
+ // is being saved we never want to display that dialog, this will also cause problems when there
+ // are server side validation issues.
+ var isSavingNewItem = false;
+
+ //we should show validation if there are any msgs in the server validation collection
+ if (serverValidationManager.items.length > 0) {
+ element.addClass(className);
+ }
+
+ var unsubscribe = [];
+
+ //listen for the forms saving event
+ unsubscribe.push(scope.$on(savingEventName, function(ev, args) {
+ element.addClass(className);
+
+ //set the flag so we can check to see if we should display the error.
+ isSavingNewItem = $routeParams.create;
+ }));
+
+ //listen for the forms saved event
+ unsubscribe.push(scope.$on(savedEvent, function(ev, args) {
+ //remove validation class
+ element.removeClass(className);
+
+ //clear form state as at this point we retrieve new data from the server
+ //and all validation will have cleared at this point
+ formCtrl.$setPristine();
+ }));
+
+ //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but
+ // the form has pending changes
+ var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) {
+ if (!formCtrl.$dirty || isSavingNewItem) {
+ return;
+ }
+
+ var path = nextLocation.split("#")[1];
+ if (path) {
+ if (path.indexOf("%253") || path.indexOf("%252")) {
+ path = decodeURIComponent(path);
+ }
+
+ if (!notificationsService.hasView()) {
+ var msg = { view: "confirmroutechange", args: { path: path, listener: locationEvent } };
+ notificationsService.add(msg);
+ }
+
+ //prevent the route!
+ event.preventDefault();
+
+ //raise an event
+ eventsService.emit("valFormManager.pendingChanges", true);
+ }
+
+ });
+ unsubscribe.push(locationEvent);
+
+ //Ensure to remove the event handler when this instance is destroyted
+ scope.$on('$destroy', function() {
+ for (var u in unsubscribe) {
+ unsubscribe[u]();
+ }
+ });
+
+ $timeout(function(){
+ formCtrl.$setPristine();
+ }, 1000);
+ }
+ };
+}
+angular.module('umbraco.directives.validation').directive("valFormManager", valFormManager);
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:valPropertyMsg
+* @restrict A
+* @element textarea
+* @requires formController
+* @description This directive is used to control the display of the property level validation message.
+* We will listen for server side validation changes
+* and when an error is detected for this property we'll show the error message.
+* In order for this directive to work, the valStatusChanged directive must be placed on the containing form.
+**/
+function valPropertyMsg(serverValidationManager) {
+
+ return {
+ scope: {
+ property: "="
+ },
+ require: "^form", //require that this directive is contained within an ngForm
+ replace: true, //replace the element with the template
+ restrict: "E", //restrict to element
+ template: "
{{errorMsg}}
",
+
+ /**
+ Our directive requries a reference to a form controller
+ which gets passed in to this parameter
+ */
+ link: function (scope, element, attrs, formCtrl) {
+
+ var watcher = null;
+
+ // Gets the error message to display
+ function getErrorMsg() {
+ //this can be null if no property was assigned
+ if (scope.property) {
+ //first try to get the error msg from the server collection
+ var err = serverValidationManager.getPropertyError(scope.property.alias, "");
+ //if there's an error message use it
+ if (err && err.errorMsg) {
+ return err.errorMsg;
+ }
+ else {
+ return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors";
+ }
+
+ }
+ return "Property has errors";
+ }
+
+ // We need to subscribe to any changes to our model (based on user input)
+ // This is required because when we have a server error we actually invalidate
+ // the form which means it cannot be resubmitted.
+ // So once a field is changed that has a server error assigned to it
+ // we need to re-validate it for the server side validator so the user can resubmit
+ // the form. Of course normal client-side validators will continue to execute.
+ function startWatch() {
+ //if there's not already a watch
+ if (!watcher) {
+ watcher = scope.$watch("property.value", function (newValue, oldValue) {
+
+ if (!newValue || angular.equals(newValue, oldValue)) {
+ return;
+ }
+
+ var errCount = 0;
+ for (var e in formCtrl.$error) {
+ if (angular.isArray(formCtrl.$error[e])) {
+ errCount++;
+ }
+ }
+
+ //we are explicitly checking for valServer errors here, since we shouldn't auto clear
+ // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
+ // is the only one, then we'll clear.
+
+ if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
+ scope.errorMsg = "";
+ formCtrl.$setValidity('valPropertyMsg', true);
+ stopWatch();
+ }
+ }, true);
+ }
+ }
+
+ //clear the watch when the property validator is valid again
+ function stopWatch() {
+ if (watcher) {
+ watcher();
+ watcher = null;
+ }
+ }
+
+ //if there's any remaining errors in the server validation service then we should show them.
+ var showValidation = serverValidationManager.items.length > 0;
+ var hasError = false;
+
+ //create properties on our custom scope so we can use it in our template
+ scope.errorMsg = "";
+
+ var unsubscribe = [];
+
+ //listen for form error changes
+ unsubscribe.push(scope.$on("valStatusChanged", function(evt, args) {
+ if (args.form.$invalid) {
+
+ //first we need to check if the valPropertyMsg validity is invalid
+ if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) {
+ //since we already have an error we'll just return since this means we've already set the
+ // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe
+ return;
+ }
+ else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) {
+ //check if it's one of the properties that is invalid in the current content property
+ hasError = true;
+ //update the validation message if we don't already have one assigned.
+ if (showValidation && scope.errorMsg === "") {
+ scope.errorMsg = getErrorMsg();
+ }
+ }
+ else {
+ hasError = false;
+ scope.errorMsg = "";
+ }
+ }
+ else {
+ hasError = false;
+ scope.errorMsg = "";
+ }
+ }, true));
+
+ //listen for the forms saving event
+ unsubscribe.push(scope.$on("formSubmitting", function(ev, args) {
+ showValidation = true;
+ if (hasError && scope.errorMsg === "") {
+ scope.errorMsg = getErrorMsg();
+ }
+ else if (!hasError) {
+ scope.errorMsg = "";
+ stopWatch();
+ }
+ }));
+
+ //listen for the forms saved event
+ unsubscribe.push(scope.$on("formSubmitted", function(ev, args) {
+ showValidation = false;
+ scope.errorMsg = "";
+ formCtrl.$setValidity('valPropertyMsg', true);
+ stopWatch();
+ }));
+
+ //listen for server validation changes
+ // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for
+ // validation changes to fields in the property this is because some server side validators may not
+ // return the field name for which the error belongs too, just the property for which it belongs.
+ // It's important to note that we need to subscribe to server validation changes here because we always must
+ // indicate that a content property is invalid at the property level since developers may not actually implement
+ // the correct field validation in their property editors.
+
+ if (scope.property) { //this can be null if no property was assigned
+ serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) {
+ hasError = !isValid;
+ if (hasError) {
+ //set the error message to the server message
+ scope.errorMsg = propertyErrors[0].errorMsg;
+ //flag that the current validator is invalid
+ formCtrl.$setValidity('valPropertyMsg', false);
+ startWatch();
+ }
+ else {
+ scope.errorMsg = "";
+ //flag that the current validator is valid
+ formCtrl.$setValidity('valPropertyMsg', true);
+ stopWatch();
+ }
+ });
+
+ //when the element is disposed we need to unsubscribe!
+ // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
+ // but they are a different callback instance than the above.
+ element.bind('$destroy', function () {
+ stopWatch();
+ serverValidationManager.unsubscribe(scope.property.alias, "");
+ });
+ }
+
+ //when the scope is disposed we need to unsubscribe
+ scope.$on('$destroy', function () {
+ for (var u in unsubscribe) {
+ unsubscribe[u]();
+ }
+ });
+ }
+
+
+ };
+}
+angular.module('umbraco.directives.validation').directive("valPropertyMsg", valPropertyMsg);
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:valPropertyValidator
+* @restrict A
+* @description Performs any custom property value validation checks on the client side. This allows property editors to be highly flexible when it comes to validation
+ on the client side. Typically if a property editor stores a primitive value (i.e. string) then the client side validation can easily be taken care of
+ with standard angular directives such as ng-required. However since some property editors store complex data such as JSON, a given property editor
+ might require custom validation. This directive can be used to validate an Umbraco property in any way that a developer would like by specifying a
+ callback method to perform the validation. The result of this method must return an object in the format of
+ {isValid: true, errorKey: 'required', errorMsg: 'Something went wrong' }
+ The error message returned will also be displayed for the property level validation message.
+ This directive should only be used when dealing with complex models, if custom validation needs to be performed with primitive values, use the simpler
+ angular validation directives instead since this will watch the entire model.
+**/
+
+function valPropertyValidator(serverValidationManager) {
+ return {
+ scope: {
+ valPropertyValidator: "="
+ },
+
+ // The element must have ng-model attribute and be inside an umbProperty directive
+ require: ['ngModel', '?^umbProperty'],
+
+ restrict: "A",
+
+ link: function (scope, element, attrs, ctrls) {
+
+ var modelCtrl = ctrls[0];
+ var propCtrl = ctrls.length > 1 ? ctrls[1] : null;
+
+ // Check whether the scope has a valPropertyValidator method
+ if (!scope.valPropertyValidator || !angular.isFunction(scope.valPropertyValidator)) {
+ throw new Error('val-property-validator directive must specify a function to call');
+ }
+
+ var initResult = scope.valPropertyValidator();
+
+ // Validation method
+ var validate = function (viewValue) {
+ // Calls the validition method
+ var result = scope.valPropertyValidator();
+ if (!result.errorKey || result.isValid === undefined || !result.errorMsg) {
+ throw "The result object from valPropertyValidator does not contain required properties: isValid, errorKey, errorMsg";
+ }
+ if (result.isValid === true) {
+ // Tell the controller that the value is valid
+ modelCtrl.$setValidity(result.errorKey, true);
+ if (propCtrl) {
+ propCtrl.setPropertyError(null);
+ }
+ }
+ else {
+ // Tell the controller that the value is invalid
+ modelCtrl.$setValidity(result.errorKey, false);
+ if (propCtrl) {
+ propCtrl.setPropertyError(result.errorMsg);
+ }
+ }
+ };
+
+ // Parsers are called as soon as the value in the form input is modified
+ modelCtrl.$parsers.push(validate);
+
+ }
+ };
+}
+angular.module('umbraco.directives.validation').directive("valPropertyValidator", valPropertyValidator);
+
+/**
+ * @ngdoc directive
+ * @name umbraco.directives.directive:valRegex
+ * @restrict A
+ * @description A custom directive to allow for matching a value against a regex string.
+ * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string
+ **/
+function valRegex() {
+
+ return {
+ require: 'ngModel',
+ restrict: "A",
+ link: function (scope, elm, attrs, ctrl) {
+
+ var flags = "";
+ var regex;
+ var eventBindings = [];
+
+ attrs.$observe("valRegexFlags", function (newVal) {
+ if (newVal) {
+ flags = newVal;
+ }
+ });
+
+ attrs.$observe("valRegex", function (newVal) {
+ if (newVal) {
+ try {
+ var resolved = newVal;
+ if (resolved) {
+ regex = new RegExp(resolved, flags);
+ }
+ else {
+ regex = new RegExp(attrs.valRegex, flags);
+ }
+ }
+ catch (e) {
+ regex = new RegExp(attrs.valRegex, flags);
+ }
+ }
+ });
+
+ eventBindings.push(scope.$watch('ngModel', function(newValue, oldValue){
+ if(newValue && newValue !== oldValue) {
+ patternValidator(newValue);
+ }
+ }));
+
+ var patternValidator = function (viewValue) {
+ if (regex) {
+ //NOTE: we don't validate on empty values, use required validator for that
+ if (!viewValue || regex.test(viewValue.toString())) {
+ // it is valid
+ ctrl.$setValidity('valRegex', true);
+ //assign a message to the validator
+ ctrl.errorMsg = "";
+ return viewValue;
+ }
+ else {
+ // it is invalid, return undefined (no model update)
+ ctrl.$setValidity('valRegex', false);
+ //assign a message to the validator
+ ctrl.errorMsg = "Value is invalid, it does not match the correct pattern";
+ return undefined;
+ }
+ }
+ };
+
+ scope.$on('$destroy', function(){
+ // unbind watchers
+ for(var e in eventBindings) {
+ eventBindings[e]();
+ }
+ });
+
+ }
+ };
+}
+angular.module('umbraco.directives.validation').directive("valRegex", valRegex);
+
+(function() {
+ 'use strict';
+
+ function ValRequireComponentDirective() {
+
+ function link(scope, el, attr, ngModel) {
+
+ var unbindModelWatcher = scope.$watch(function () {
+ return ngModel.$modelValue;
+ }, function(newValue) {
+
+ if(newValue === undefined || newValue === null || newValue === "") {
+ ngModel.$setValidity("valRequiredComponent", false);
+ } else {
+ ngModel.$setValidity("valRequiredComponent", true);
+ }
+
+ });
+
+ // clean up
+ scope.$on('$destroy', function(){
+ unbindModelWatcher();
+ });
+
+ }
+
+ var directive = {
+ require: 'ngModel',
+ restrict: "A",
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('valRequireComponent', ValRequireComponentDirective);
+
+})();
+
+/**
+ * @ngdoc directive
+ * @name umbraco.directives.directive:valServer
+ * @restrict A
+ * @description This directive is used to associate a content property with a server-side validation response
+ * so that the validators in angular are updated based on server-side feedback.
+ **/
+function valServer(serverValidationManager) {
+ return {
+ require: ['ngModel', '?^umbProperty'],
+ restrict: "A",
+ link: function (scope, element, attr, ctrls) {
+
+ var modelCtrl = ctrls[0];
+ var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null;
+ if (!umbPropCtrl) {
+ //we cannot proceed, this validator will be disabled
+ return;
+ }
+
+ var watcher = null;
+
+ //Need to watch the value model for it to change, previously we had subscribed to
+ //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that
+ // doesn't specifically have a 2 way ng binding. This is required because when we
+ // have a server error we actually invalidate the form which means it cannot be
+ // resubmitted. So once a field is changed that has a server error assigned to it
+ // we need to re-validate it for the server side validator so the user can resubmit
+ // the form. Of course normal client-side validators will continue to execute.
+ function startWatch() {
+ //if there's not already a watch
+ if (!watcher) {
+ watcher = scope.$watch(function () {
+ return modelCtrl.$modelValue;
+ }, function (newValue, oldValue) {
+
+ if (!newValue || angular.equals(newValue, oldValue)) {
+ return;
+ }
+
+ if (modelCtrl.$invalid) {
+ modelCtrl.$setValidity('valServer', true);
+ stopWatch();
+ }
+ }, true);
+ }
+ }
+
+ function stopWatch() {
+ if (watcher) {
+ watcher();
+ watcher = null;
+ }
+ }
+
+ var currentProperty = umbPropCtrl.property;
+
+ //default to 'value' if nothing is set
+ var fieldName = "value";
+ if (attr.valServer) {
+ fieldName = scope.$eval(attr.valServer);
+ if (!fieldName) {
+ //eval returned nothing so just use the string
+ fieldName = attr.valServer;
+ }
+ }
+
+ //subscribe to the server validation changes
+ serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) {
+ if (!isValid) {
+ modelCtrl.$setValidity('valServer', false);
+ //assign an error msg property to the current validator
+ modelCtrl.errorMsg = propertyErrors[0].errorMsg;
+ startWatch();
+ }
+ else {
+ modelCtrl.$setValidity('valServer', true);
+ //reset the error message
+ modelCtrl.errorMsg = "";
+ stopWatch();
+ }
+ });
+
+ //when the element is disposed we need to unsubscribe!
+ // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
+ // but they are a different callback instance than the above.
+ element.bind('$destroy', function () {
+ stopWatch();
+ serverValidationManager.unsubscribe(currentProperty.alias, fieldName);
+ });
+ }
+ };
+}
+angular.module('umbraco.directives.validation').directive("valServer", valServer);
+/**
+ * @ngdoc directive
+ * @name umbraco.directives.directive:valServerField
+ * @restrict A
+ * @description This directive is used to associate a content field (not user defined) with a server-side validation response
+ * so that the validators in angular are updated based on server-side feedback.
+ **/
+function valServerField(serverValidationManager) {
+ return {
+ require: 'ngModel',
+ restrict: "A",
+ link: function (scope, element, attr, ctrl) {
+
+ var fieldName = null;
+ var eventBindings = [];
+
+ attr.$observe("valServerField", function (newVal) {
+ if (newVal && fieldName === null) {
+ fieldName = newVal;
+
+ //subscribe to the changed event of the view model. This is required because when we
+ // have a server error we actually invalidate the form which means it cannot be
+ // resubmitted. So once a field is changed that has a server error assigned to it
+ // we need to re-validate it for the server side validator so the user can resubmit
+ // the form. Of course normal client-side validators will continue to execute.
+ eventBindings.push(scope.$watch('ngModel', function(newValue){
+ if (ctrl.$invalid) {
+ ctrl.$setValidity('valServerField', true);
+ }
+ }));
+
+ //subscribe to the server validation changes
+ serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) {
+ if (!isValid) {
+ ctrl.$setValidity('valServerField', false);
+ //assign an error msg property to the current validator
+ ctrl.errorMsg = fieldErrors[0].errorMsg;
+ }
+ else {
+ ctrl.$setValidity('valServerField', true);
+ //reset the error message
+ ctrl.errorMsg = "";
+ }
+ });
+
+ //when the element is disposed we need to unsubscribe!
+ // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
+ // but they are a different callback instance than the above.
+ element.bind('$destroy', function () {
+ serverValidationManager.unsubscribe(null, fieldName);
+ });
+ }
+ });
+
+ scope.$on('$destroy', function(){
+ // unbind watchers
+ for(var e in eventBindings) {
+ eventBindings[e]();
+ }
+ });
+
+ }
+ };
+}
+angular.module('umbraco.directives.validation').directive("valServerField", valServerField);
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:valSubView
+* @restrict A
+* @description Used to show validation warnings for a editor sub view to indicate that the section content has validation errors in its data.
+* In order for this directive to work, the valFormManager directive must be placed on the containing form.
+**/
+(function() {
+ 'use strict';
+
+ function valSubViewDirective() {
+
+ function link(scope, el, attr, ctrl) {
+
+ var valFormManager = ctrl[1];
+ scope.subView.hasError = false;
+
+ //listen for form validation changes
+ valFormManager.onValidationStatusChanged(function (evt, args) {
+ if (!args.form.$valid) {
+
+ var subViewContent = el.find(".ng-invalid");
+
+ if (subViewContent.length > 0) {
+ scope.subView.hasError = true;
+ } else {
+ scope.subView.hasError = false;
+ }
+
+ }
+ else {
+ scope.subView.hasError = false;
+ }
+ });
+
+ }
+
+ var directive = {
+ require: ['^form', '^valFormManager'],
+ restrict: "A",
+ link: link
+ };
+
+ return directive;
+ }
+
+ angular.module('umbraco.directives').directive('valSubView', valSubViewDirective);
+
+})();
+
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:valTab
+* @restrict A
+* @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data.
+* In order for this directive to work, the valFormManager directive must be placed on the containing form.
+**/
+function valTab() {
+ return {
+ require: ['^form', '^valFormManager'],
+ restrict: "A",
+ link: function (scope, element, attr, ctrs) {
+
+ var valFormManager = ctrs[1];
+ var tabId = "tab" + scope.tab.id;
+ scope.tabHasError = false;
+
+ //listen for form validation changes
+ valFormManager.onValidationStatusChanged(function (evt, args) {
+ if (!args.form.$valid) {
+ var tabContent = element.closest(".umb-panel").find("#" + tabId);
+ //check if the validation messages are contained inside of this tabs
+ if (tabContent.find(".ng-invalid").length > 0) {
+ scope.tabHasError = true;
+ } else {
+ scope.tabHasError = false;
+ }
+ }
+ else {
+ scope.tabHasError = false;
+ }
+ });
+
+ }
+ };
+}
+angular.module('umbraco.directives.validation').directive("valTab", valTab);
+function valToggleMsg(serverValidationManager) {
+ return {
+ require: "^form",
+ restrict: "A",
+
+ /**
+ Our directive requries a reference to a form controller which gets passed in to this parameter
+ */
+ link: function (scope, element, attr, formCtrl) {
+
+ if (!attr.valToggleMsg){
+ throw "valToggleMsg requires that a reference to a validator is specified";
+ }
+ if (!attr.valMsgFor){
+ throw "valToggleMsg requires that the attribute valMsgFor exists on the element";
+ }
+ if (!formCtrl[attr.valMsgFor]) {
+ throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name;
+ }
+
+ //if there's any remaining errors in the server validation service then we should show them.
+ var showValidation = serverValidationManager.items.length > 0;
+ var hasCustomMsg = element.contents().length > 0;
+
+ //add a watch to the validator for the value (i.e. myForm.value.$error.required )
+ scope.$watch(function () {
+ //sometimes if a dialog closes in the middle of digest we can get null references here
+
+ return (formCtrl && formCtrl[attr.valMsgFor]) ? formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] : null;
+ }, function () {
+ //sometimes if a dialog closes in the middle of digest we can get null references here
+ if ((formCtrl && formCtrl[attr.valMsgFor])) {
+ if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) {
+ element.show();
+ //display the error message if this element has no contents
+ if (!hasCustomMsg) {
+ element.html(formCtrl[attr.valMsgFor].errorMsg);
+ }
+ }
+ else {
+ element.hide();
+ }
+ }
+ });
+
+ var unsubscribe = [];
+
+ //listen for the saving event (the result is a callback method which is called to unsubscribe)
+ unsubscribe.push(scope.$on("formSubmitting", function(ev, args) {
+ showValidation = true;
+ if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) {
+ element.show();
+ //display the error message if this element has no contents
+ if (!hasCustomMsg) {
+ element.html(formCtrl[attr.valMsgFor].errorMsg);
+ }
+ }
+ else {
+ element.hide();
+ }
+ }));
+
+ //listen for the saved event (the result is a callback method which is called to unsubscribe)
+ unsubscribe.push(scope.$on("formSubmitted", function(ev, args) {
+ showValidation = false;
+ element.hide();
+ }));
+
+ //when the element is disposed we need to unsubscribe!
+ // NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom
+ // element might still be there even after the modal has been hidden.
+ element.bind('$destroy', function () {
+ for (var u in unsubscribe) {
+ unsubscribe[u]();
+ }
+ });
+
+ }
+ };
+}
+
+/**
+* @ngdoc directive
+* @name umbraco.directives.directive:valToggleMsg
+* @restrict A
+* @element input
+* @requires formController
+* @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ?
+**/
+angular.module('umbraco.directives.validation').directive("valToggleMsg", valToggleMsg);
+angular.module('umbraco.directives.validation')
+.directive('valTriggerChange', function($sniffer) {
+ return {
+ link : function(scope, elem, attrs) {
+ elem.bind('click', function(){
+ $(attrs.valTriggerChange).trigger($sniffer.hasEvent('input') ? 'input' : 'change');
+ });
+ },
+ priority : 1
+ };
+});
+
+})();
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbraco.filters.js b/WebCms/Umbraco/Js/umbraco.filters.js
new file mode 100644
index 0000000..3d9e7fb
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.filters.js
@@ -0,0 +1,53 @@
+/*! umbraco
+ * https://github.com/umbraco/umbraco-cms/
+ * Copyright (c) 2016 Umbraco HQ;
+ * Licensed
+ */
+
+(function() {
+
+angular.module('umbraco.filters', []);
+angular.module("umbraco.filters")
+ .filter('compareArrays', function() {
+ return function inArray(array, compareArray, compareProperty) {
+
+ var result = [];
+
+ angular.forEach(array, function(arrayItem){
+
+ var exists = false;
+
+ angular.forEach(compareArray, function(compareItem){
+ if( arrayItem[compareProperty] === compareItem[compareProperty]) {
+ exists = true;
+ }
+ });
+
+ if(!exists) {
+ result.push(arrayItem);
+ }
+
+ });
+
+ return result;
+
+ };
+});
+
+angular.module("umbraco.filters").filter('timespan', function() {
+ return function(input) {
+ var sec_num = parseInt(input, 10);
+ var hours = Math.floor(sec_num / 3600);
+ var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
+ var seconds = sec_num - (hours * 3600) - (minutes * 60);
+
+ if (hours < 10) {hours = "0"+hours;}
+ if (minutes < 10) {minutes = "0"+minutes;}
+ if (seconds < 10) {seconds = "0"+seconds;}
+ var time = hours+':'+minutes+':'+seconds;
+ return time;
+ };
+ });
+
+
+})();
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbraco.httpbackend.js b/WebCms/Umbraco/Js/umbraco.httpbackend.js
new file mode 100644
index 0000000..92c2a67
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.httpbackend.js
@@ -0,0 +1,31 @@
+var umbracoAppDev = angular.module('umbraco.httpbackend', ['umbraco', 'ngMockE2E', 'umbraco.mocks']);
+
+
+function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMocks, contentTypeMocks, sectionMocks, entityMocks, dataTypeMocks, dashboardMocks, macroMocks, utilMocks, localizationMocks, prevaluesMocks) {
+
+ console.log("httpBackend inited");
+
+ //Register mocked http responses
+ contentMocks.register();
+ mediaMocks.register();
+ sectionMocks.register();
+ treeMocks.register();
+ dataTypeMocks.register();
+ dashboardMocks.register();
+ userMocks.register();
+ macroMocks.register();
+ contentTypeMocks.register();
+ utilMocks.register();
+ localizationMocks.register();
+ prevaluesMocks.register();
+ entityMocks.register();
+
+ $httpBackend.whenGET(/^..\/config\//).passThrough();
+ $httpBackend.whenGET(/^views\//).passThrough();
+ $httpBackend.whenGET(/^js\//).passThrough();
+ $httpBackend.whenGET(/^lib\//).passThrough();
+ $httpBackend.whenGET(/^assets\//).passThrough();
+}
+
+
+umbracoAppDev.run(initBackEnd);
diff --git a/WebCms/Umbraco/Js/umbraco.installer.js b/WebCms/Umbraco/Js/umbraco.installer.js
new file mode 100644
index 0000000..e7eb30f
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.installer.js
@@ -0,0 +1,468 @@
+/*! umbraco
+ * https://github.com/umbraco/umbraco-cms/
+ * Copyright (c) 2016 Umbraco HQ;
+ * Licensed
+ */
+
+(function() {
+
+ angular.module('umbraco.install', []);
+angular.module("umbraco.install").controller("Umbraco.InstallerController",
+ function ($scope, installerService) {
+
+ //TODO: Decouple the service from the controller - the controller should be responsible
+ // for the model (state) and the service should be responsible for helping the controller,
+ // the controller should be passing the model into it's methods for manipulation and not hold
+ // state. We should not be assigning properties from a service to a controller's scope.
+ // see: https://github.com/umbraco/Umbraco-CMS/commit/b86ef0d7ac83f699aee35d807f7f7ebb6dd0ed2c#commitcomment-5721204
+
+ $scope.stepIndex = 0;
+ //comment this out if you just want to see tips
+ installerService.init();
+
+ //uncomment this to see tips
+ //installerService.switchToFeedback();
+
+ $scope.installer = installerService.status;
+
+ $scope.forward = function () {
+ installerService.forward();
+ };
+
+ $scope.backward = function () {
+ installerService.backward();
+ };
+
+ $scope.install = function () {
+ installerService.install();
+ };
+
+ $scope.gotoStep = function (step) {
+ installerService.gotoNamedStep(step);
+ };
+
+ $scope.restart = function () {
+ installerService.gotoStep(0);
+ };
+ });
+
+//this ensure that we start with a clean slate on every install and upgrade
+angular.module("umbraco.install").run(function ($templateCache) {
+ $templateCache.removeAll();
+});
+angular.module("umbraco.install").factory('installerService', function($rootScope, $q, $timeout, $http, $location, $log){
+
+ var _status = {
+ index: 0,
+ current: undefined,
+ steps: undefined,
+ loading: true,
+ progress: "100%"
+ };
+
+ var factTimer = undefined;
+ var _installerModel = {
+ installId: undefined,
+ instructions: {
+ }
+ };
+
+ //add to umbraco installer facts here
+ var facts = ['Umbraco helped millions of people watch a man jump from the edge of space',
+ 'Over 370 000 websites are currently powered by Umbraco',
+ "At least 2 people have named their cat 'Umbraco'",
+ 'On an average day, more than 1000 people download Umbraco',
+ 'umbraco.tv is the premier source of Umbraco video tutorials to get you started',
+ 'You can find the world\'s friendliest CMS community at our.umbraco.org',
+ 'You can become a certified Umbraco developer by attending one of the official courses',
+ 'Umbraco works really well on tablets',
+ 'You have 100% control over your markup and design when crafting a website in Umbraco',
+ 'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company',
+ "There's a pretty big chance, you've visited a website powered by Umbraco today",
+ "'Umbraco-spotting' is the game of spotting big brands running Umbraco",
+ "At least 4 people have the Umbraco logo tattooed on them",
+ "'Umbraco' is the danish name for an allen key",
+ "Umbraco has been around since 2005, that's a looong time in IT",
+ "More than 400 people from all over the world meet each year in Denmark in June for our annual conference CodeGarden",
+ "While you are installing Umbraco someone else on the other side of the planet is probably doing it too",
+ "You can extend Umbraco without modifying the source code using either JavaScript or C#",
+ "Umbraco was installed in more than 165 countries in 2015"
+ ];
+
+ /**
+ Returns the description for the step at a given index based on the order of the serverOrder of steps
+ Since they don't execute on the server in the order that they are displayed in the UI.
+ */
+ function getDescriptionForStepAtIndex(steps, index) {
+ var sorted = _.sortBy(steps, "serverOrder");
+ if (sorted[index]) {
+ return sorted[index].description;
+ }
+ return null;
+ }
+ /* Returns the description for the given step name */
+ function getDescriptionForStepName(steps, name) {
+ var found = _.find(steps, function(i) {
+ return i.name == name;
+ });
+ return (found) ? found.description : null;
+ }
+
+ //calculates the offset of the progressbar on the installer
+ function calculateProgress(steps, next) {
+ var sorted = _.sortBy(steps, "serverOrder");
+
+ var pct = "100%";
+ for (var i = sorted.length - 1; i >= 0; i--) {
+ if(sorted[i].name == next){
+ pct = Math.floor((i+1) / steps.length * 100) + "%";
+ break;
+ }
+ }
+ return pct;
+ }
+
+ //helpful defaults for the view loading
+ function resolveView(view){
+
+ if(view.indexOf(".html") < 0){
+ view = view + ".html";
+ }
+ if(view.indexOf("/") < 0){
+ view = "views/install/" + view;
+ }
+
+ return view;
+ }
+
+ /** Have put this here because we are not referencing our other modules */
+ function safeApply (scope, fn) {
+ if (scope.$$phase || scope.$root.$$phase) {
+ if (angular.isFunction(fn)) {
+ fn();
+ }
+ }
+ else {
+ if (angular.isFunction(fn)) {
+ scope.$apply(fn);
+ }
+ else {
+ scope.$apply();
+ }
+ }
+ }
+
+ var service = {
+
+ status : _status,
+ //loads the needed steps and sets the intial state
+ init : function(){
+ service.status.loading = true;
+ if(!_status.all){
+ service.getSteps().then(function(response){
+ service.status.steps = response.data.steps;
+ service.status.index = 0;
+ _installerModel.installId = response.data.installId;
+ service.findNextStep();
+
+ $timeout(function(){
+ service.status.loading = false;
+ service.status.configuring = true;
+ }, 2000);
+ });
+ }
+ },
+
+ //loads available packages from our.umbraco.org
+ getPackages : function(){
+ return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + "GetPackages");
+ },
+
+ getSteps : function(){
+ return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + "GetSetup");
+ },
+
+ gotoStep : function(index){
+ var step = service.status.steps[index];
+ step.view = resolveView(step.view);
+
+ if(!step.model){
+ step.model = {};
+ }
+
+ service.status.index = index;
+ service.status.current = step;
+ service.retrieveCurrentStep();
+ },
+
+ gotoNamedStep : function(stepName){
+ var step = _.find(service.status.steps, function(s, index){
+ if (s.view && s.name === stepName) {
+ service.status.index = index;
+ return true;
+ }
+ return false;
+ });
+
+ step.view = resolveView(step.view);
+ if(!step.model){
+ step.model = {};
+ }
+ service.retrieveCurrentStep();
+ service.status.current = step;
+ },
+
+ /**
+ Finds the next step containing a view. If one is found it stores it as the current step
+ and retreives the step information and returns it, otherwise returns null .
+ */
+ findNextStep : function(){
+ var step = _.find(service.status.steps, function(s, index){
+ if(s.view && index >= service.status.index){
+ service.status.index = index;
+ return true;
+ }
+ return false;
+ });
+
+ if (step) {
+ if (step.view.indexOf(".html") < 0) {
+ step.view = step.view + ".html";
+ }
+
+ if (step.view.indexOf("/") < 0) {
+ step.view = "views/install/" + step.view;
+ }
+
+ if (!step.model) {
+ step.model = {};
+ }
+
+ service.status.current = step;
+ service.retrieveCurrentStep();
+
+ //returns the next found step
+ return step;
+ }
+ else {
+ //there are no more steps found containing a view so return null
+ return null;
+ }
+ },
+
+ storeCurrentStep : function(){
+ _installerModel.instructions[service.status.current.name] = service.status.current.model;
+ },
+
+ retrieveCurrentStep : function(){
+ if(_installerModel.instructions[service.status.current.name]){
+ service.status.current.model = _installerModel.instructions[service.status.current.name];
+ }
+ },
+
+ /** Moves the installer forward to the next view, if there are not more views than the installation will commence */
+ forward : function(){
+ service.storeCurrentStep();
+ service.status.index++;
+ var found = service.findNextStep();
+ if (!found) {
+ //no more steps were found so start the installation process
+ service.install();
+ }
+ },
+
+ backwards : function(){
+ service.storeCurrentStep();
+ service.gotoStep(service.status.index--);
+ },
+
+ install : function(){
+ service.storeCurrentStep();
+ service.switchToFeedback();
+
+ service.status.feedback = getDescriptionForStepAtIndex(service.status.steps, 0);
+ service.status.progress = 0;
+
+ function processInstallStep() {
+
+ $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostPerformInstall", _installerModel)
+ .success(function(data, status, headers, config) {
+ if (!data.complete) {
+
+ //progress feedback
+ service.status.progress = calculateProgress(service.status.steps, data.nextStep);
+
+ if (data.view) {
+ //set the current view and model to whatever the process returns, the view is responsible for retriggering install();
+ var v = resolveView(data.view);
+ service.status.current = { view: v, model: data.model };
+
+ //turn off loading bar and feedback
+ service.switchToConfiguration();
+ }
+ else {
+ var desc = getDescriptionForStepName(service.status.steps, data.nextStep);
+ if (desc) {
+ service.status.feedback = desc;
+ }
+ processInstallStep();
+ }
+ }
+ else {
+ service.complete();
+ }
+ }).error(function(data, status, headers, config) {
+ //need to handle 500's separately, this will happen if something goes wrong outside
+ // of the installer (like app startup events or something) and these will get returned as text/html
+ // not as json. If this happens we can't actually load in external views since they will YSOD as well!
+ // so we need to display this in our own internal way
+
+ if (status >= 500 && status < 600) {
+ service.status.current = { view: "ysod", model: null };
+ var ysod = data;
+ //we need to manually write the html to the iframe - the html contains full html markup
+ $timeout(function () {
+ document.getElementById('ysod').contentDocument.write(ysod);
+ }, 500);
+ }
+ else {
+ //this is where we handle installer error
+ var v = data.view ? resolveView(data.view) : resolveView("error");
+ var model = data.model ? data.model : data;
+ service.status.current = { view: v, model: model };
+ }
+
+ service.switchToConfiguration();
+
+ });
+ }
+ processInstallStep();
+ },
+
+ randomFact: function () {
+ safeApply($rootScope, function() {
+ service.status.fact = facts[_.random(facts.length - 1)];
+ });
+ },
+
+ switchToFeedback : function(){
+ service.status.current = undefined;
+ service.status.loading = true;
+ service.status.configuring = false;
+
+ //initial fact
+ service.randomFact();
+
+ //timed facts
+ factTimer = window.setInterval(function(){
+ service.randomFact();
+ },6000);
+ },
+
+ switchToConfiguration : function(){
+ service.status.loading = false;
+ service.status.configuring = true;
+ service.status.feedback = undefined;
+ service.status.fact = undefined;
+
+ if(factTimer){
+ clearInterval(factTimer);
+ }
+ },
+
+ complete : function(){
+
+ service.status.progress = "100%";
+ service.status.done = true;
+ service.status.feedback = "Redirecting you to Umbraco, please wait";
+ service.status.loading = false;
+
+ if(factTimer){
+ clearInterval(factTimer);
+ }
+
+ $timeout(function(){
+ window.location.href = Umbraco.Sys.ServerVariables.umbracoBaseUrl;
+ }, 1500);
+ }
+ };
+
+ return service;
+});
+
+angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseController", function($scope, $http, installerService){
+
+ $scope.checking = false;
+ $scope.dbs = [
+ {name: 'Microsoft SQL Server Compact (SQL CE)', id: 0},
+ {name: 'Microsoft SQL Server', id: 1},
+ { name: 'Microsoft SQL Azure', id: 3 },
+ { name: 'MySQL', id: 2 },
+ {name: 'Custom connection string', id: -1}];
+
+ if(installerService.status.current.model.dbType === undefined){
+ installerService.status.current.model.dbType = 0;
+ }
+
+ $scope.validateAndForward = function(){
+ if(!$scope.checking && this.myForm.$valid){
+ $scope.checking = true;
+ var model = installerService.status.current.model;
+
+ $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection",
+ model).then(function(response){
+
+ if(response.data === "true"){
+ installerService.forward();
+ }else{
+ $scope.invalidDbDns = true;
+ }
+
+ $scope.checking = false;
+ }, function(){
+ $scope.invalidDbDns = true;
+ $scope.checking = false;
+ });
+ }
+ };
+});
+angular.module("umbraco.install").controller("Umbraco.Installer.PackagesController", function ($scope, installerService) {
+
+ installerService.getPackages().then(function (response) {
+ $scope.packages = response.data;
+ });
+
+ $scope.setPackageAndContinue = function (pckId) {
+ installerService.status.current.model = pckId;
+ installerService.forward();
+ };
+
+});
+angular.module("umbraco.install").controller("Umbraco.Install.UserController", function($scope, installerService) {
+
+ $scope.passwordPattern = /.*/;
+ $scope.installer.current.model.subscribeToNewsLetter = true;
+
+ if ($scope.installer.current.model.minNonAlphaNumericLength > 0) {
+ var exp = "";
+ for (var i = 0; i < $scope.installer.current.model.minNonAlphaNumericLength; i++) {
+ exp += ".*[\\W].*";
+ }
+ //replace duplicates
+ exp = exp.replace(".*.*", ".*");
+ $scope.passwordPattern = new RegExp(exp);
+ }
+
+ $scope.validateAndInstall = function(){
+ installerService.install();
+ };
+
+ $scope.validateAndForward = function(){
+ if(this.myForm.$valid){
+ installerService.forward();
+ }
+ };
+
+});
+
+})();
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbraco.resources.js b/WebCms/Umbraco/Js/umbraco.resources.js
new file mode 100644
index 0000000..d2e1852
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.resources.js
@@ -0,0 +1,4146 @@
+/*! umbraco
+ * https://github.com/umbraco/umbraco-cms/
+ * Copyright (c) 2016 Umbraco HQ;
+ * Licensed
+ */
+
+(function() {
+
+angular.module("umbraco.resources", []);
+/**
+ * @ngdoc service
+ * @name umbraco.resources.authResource
+ * @description
+ * This Resource perfomrs actions to common authentication tasks for the Umbraco backoffice user
+ *
+ * @requires $q
+ * @requires $http
+ * @requires umbRequestHelper
+ * @requires angularHelper
+ */
+function authResource($q, $http, umbRequestHelper, angularHelper) {
+
+ return {
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.authResource#performLogin
+ * @methodOf umbraco.resources.authResource
+ *
+ * @description
+ * Logs the Umbraco backoffice user in if the credentials are good
+ *
+ * ##usage
+ *
+ * @returns {Promise} resourcePromise object
+ *
+ */
+ performLogout: function() {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "authenticationApiBaseUrl",
+ "PostLogout")));
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.authResource#getCurrentUser
+ * @methodOf umbraco.resources.authResource
+ *
+ * @description
+ * Sends a request to the server to get the current user details, will return a 401 if the user is not logged in
+ *
+ * ##usage
+ *
+ * authResource.getCurrentUser()
+ * .then(function(data) {
+ * //Do stuff for fetching the current logged in Umbraco backoffice user
+ * });
+ *
+ * @returns {Promise} resourcePromise object
+ *
+ */
+ getCurrentUser: function () {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "authenticationApiBaseUrl",
+ "GetCurrentUser")),
+ 'Server call failed for getting current user');
+ },
+
+ getCurrentUserLinkedLogins: function () {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "authenticationApiBaseUrl",
+ "GetCurrentUserLinkedLogins")),
+ 'Server call failed for getting current users linked logins');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.authResource#isAuthenticated
+ * @methodOf umbraco.resources.authResource
+ *
+ * @description
+ * Checks if the user is logged in or not - does not return 401 or 403
+ *
+ * ##usage
+ *
+ * authResource.isAuthenticated()
+ * .then(function(data) {
+ * //Do stuff to check if user is authenticated
+ * });
+ *
+ * @returns {Promise} resourcePromise object
+ *
+ */
+ isAuthenticated: function () {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "authenticationApiBaseUrl",
+ "IsAuthenticated")),
+ {
+ success: function (data, status, headers, config) {
+ //if the response is false, they are not logged in so return a rejection
+ if (data === false || data === "false") {
+ return $q.reject('User is not logged in');
+ }
+ return data;
+ },
+ error: function (data, status, headers, config) {
+ return {
+ errorMsg: 'Server call failed for checking authentication',
+ data: data,
+ status: status
+ };
+ }
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.authResource#getRemainingTimeoutSeconds
+ * @methodOf umbraco.resources.authResource
+ *
+ * @description
+ * Gets the user's remaining seconds before their login times out
+ *
+ * ##usage
+ *
+ * authResource.getRemainingTimeoutSeconds()
+ * .then(function(data) {
+ * //Number of seconds is returned
+ * });
+ *
+ * @returns {Promise} resourcePromise object
+ *
+ */
+ getRemainingTimeoutSeconds: function () {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "authenticationApiBaseUrl",
+ "GetRemainingTimeoutSeconds")),
+ 'Server call failed for checking remaining seconds');
+ }
+
+ };
+}
+
+angular.module('umbraco.resources').factory('authResource', authResource);
+
+/**
+ * @ngdoc service
+ * @name umbraco.resources.contentResource
+ * @description Handles all transactions of content data
+ * from the angular application to the Umbraco database, using the Content WebApi controller
+ *
+ * all methods returns a resource promise async, so all operations won't complete untill .then() is completed.
+ *
+ * @requires $q
+ * @requires $http
+ * @requires umbDataFormatter
+ * @requires umbRequestHelper
+ *
+ * ##usage
+ * To use, simply inject the contentResource into any controller or service that needs it, and make
+ * sure the umbraco.resources module is accesible - which it should be by default.
+ *
+ *
+ * @param {Object} args arguments object
+ * @param {Int} args.id the ID of the node to copy
+ * @param {Int} args.parentId the ID of the parent node to copy to
+ * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ copy: function (args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.parentId) {
+ throw "args.parentId cannot be null";
+ }
+ if (!args.id) {
+ throw "args.id cannot be null";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"),
+ args),
+ 'Failed to copy content');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#unPublish
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Unpublishes a content item with a given Id
+ *
+ * ##usage
+ *
+ *
+ * @param {Array} ids ids of content items to return as an array
+ * @returns {Promise} resourcePromise object containing the content items array.
+ *
+ */
+ getByIds: function (ids) {
+
+ var idQuery = "";
+ _.each(ids, function (item) {
+ idQuery += "ids=" + item + "&";
+ });
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetByIds",
+ idQuery)),
+ 'Failed to retrieve data for content with multiple ids');
+ },
+
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#getScaffold
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias.
+ *
+ * - Parent Id must be provided so umbraco knows where to store the content
+ * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold
+ *
+ * The scaffold is used to build editors for content that has not yet been populated with data.
+ *
+ * ##usage
+ *
+ * contentResource.getScaffold(1234, 'homepage')
+ * .then(function(scaffold) {
+ * var myDoc = scaffold;
+ * myDoc.name = "My new document";
+ *
+ * contentResource.publish(myDoc, true)
+ * .then(function(content){
+ * alert("Retrieved, updated and published again");
+ * });
+ * });
+ *
+ *
+ * @param {Int} parentId id of content item to return
+ * @param {String} alias contenttype alias to base the scaffold on
+ * @returns {Promise} resourcePromise object containing the content scaffold.
+ *
+ */
+ getScaffold: function (parentId, alias) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetEmpty",
+ [{ contentTypeAlias: alias }, { parentId: parentId }])),
+ 'Failed to retrieve data for empty content item type ' + alias);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#getNiceUrl
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Returns a url, given a node ID
+ *
+ * ##usage
+ *
+ *
+ * @param {Int} id Id of node to return the public url to
+ * @returns {Promise} resourcePromise object containing the url.
+ *
+ */
+ getNiceUrl: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetNiceUrl", [{ id: id }])),
+ 'Failed to retrieve url for id:' + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#getChildren
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Gets children of a content item with a given id
+ *
+ * ##usage
+ *
+ * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+ * .then(function(contentArray) {
+ * var children = contentArray;
+ * alert('they are here!');
+ * });
+ *
+ *
+ * @param {Int} parentid id of content item to return children of
+ * @param {Object} options optional options object
+ * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0
+ * @param {Int} options.pageNumber if paging data, current page index, default = 0
+ * @param {String} options.filter if provided, query will only return those with names matching the filter
+ * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending`
+ * @param {String} options.orderBy property to order items by, default: `SortOrder`
+ * @returns {Promise} resourcePromise object containing an array of content items.
+ *
+ */
+ getChildren: function (parentId, options) {
+
+ var defaults = {
+ pageSize: 0,
+ pageNumber: 0,
+ filter: '',
+ orderDirection: "Ascending",
+ orderBy: "SortOrder",
+ orderBySystemField: true
+ };
+ if (options === undefined) {
+ options = {};
+ }
+ //overwrite the defaults if there are any specified
+ angular.extend(defaults, options);
+ //now copy back to the options we will use
+ options = defaults;
+ //change asc/desct
+ if (options.orderDirection === "asc") {
+ options.orderDirection = "Ascending";
+ }
+ else if (options.orderDirection === "desc") {
+ options.orderDirection = "Descending";
+ }
+
+ //converts the value to a js bool
+ function toBool(v) {
+ if (angular.isNumber(v)) {
+ return v > 0;
+ }
+ if (angular.isString(v)) {
+ return v === "true";
+ }
+ if (typeof v === "boolean") {
+ return v;
+ }
+ return false;
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetChildren",
+ [
+ { id: parentId },
+ { pageNumber: options.pageNumber },
+ { pageSize: options.pageSize },
+ { orderBy: options.orderBy },
+ { orderDirection: options.orderDirection },
+ { orderBySystemField: toBool(options.orderBySystemField) },
+ { filter: options.filter }
+ ])),
+ 'Failed to retrieve children for content item ' + parentId);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#hasPermission
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Returns true/false given a permission char to check against a nodeID
+ * for the current user
+ *
+ * ##usage
+ *
+ * contentResource.hasPermission('p',1234)
+ * .then(function() {
+ * alert('You are allowed to publish this item');
+ * });
+ *
+ *
+ * @param {String} permission char representing the permission to check
+ * @param {Int} id id of content item to delete
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ checkPermission: function (permission, id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "HasPermission",
+ [{ permissionToCheck: permission }, { nodeId: id }])),
+ 'Failed to check permission for item ' + id);
+ },
+
+ getPermissions: function (nodeIds) {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetPermissions"),
+ nodeIds),
+ 'Failed to get permissions');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#save
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation
+ * if the content item needs to have files attached, they must be provided as the files param and passed separately
+ *
+ *
+ * ##usage
+ *
+ * contentResource.getById(1234)
+ * .then(function(content) {
+ * content.name = "I want a new name!";
+ * contentResource.save(content, false)
+ * .then(function(content){
+ * alert("Retrieved, updated and saved again");
+ * });
+ * });
+ *
+ *
+ * @param {Object} content The content item object with changes applied
+ * @param {Bool} isNew set to true to create a new item or to update an existing
+ * @param {Array} files collection of files for the document
+ * @returns {Promise} resourcePromise object containing the saved content item.
+ *
+ */
+ save: function (content, isNew, files) {
+ return saveContentItem(content, "save" + (isNew ? "New" : ""), files);
+ },
+
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#publish
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation
+ * if the content item needs to have files attached, they must be provided as the files param and passed separately
+ *
+ *
+ * ##usage
+ *
+ * contentResource.getById(1234)
+ * .then(function(content) {
+ * content.name = "I want a new name, and be published!";
+ * contentResource.publish(content, false)
+ * .then(function(content){
+ * alert("Retrieved, updated and published again");
+ * });
+ * });
+ *
+ *
+ * @param {Object} content The content item object with changes applied
+ * @param {Bool} isNew set to true to create a new item or to update an existing
+ * @param {Array} files collection of files for the document
+ * @returns {Promise} resourcePromise object containing the saved content item.
+ *
+ */
+ publish: function (content, isNew, files) {
+ return saveContentItem(content, "publish" + (isNew ? "New" : ""), files);
+ },
+
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#sendToPublish
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Saves changes made to a content item, and notifies any subscribers about a pending publication
+ *
+ * ##usage
+ *
+ * contentResource.getById(1234)
+ * .then(function(content) {
+ * content.name = "I want a new name, and be published!";
+ * contentResource.sendToPublish(content, false)
+ * .then(function(content){
+ * alert("Retrieved, updated and notication send off");
+ * });
+ * });
+ *
+ *
+ * @param {Object} content The content item object with changes applied
+ * @param {Bool} isNew set to true to create a new item or to update an existing
+ * @param {Array} files collection of files for the document
+ * @returns {Promise} resourcePromise object containing the saved content item.
+ *
+ */
+ sendToPublish: function (content, isNew, files) {
+ return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#publishByid
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Publishes a content item with a given ID
+ *
+ * ##usage
+ *
+ *
+ * @param {Int} id id of data type to retrieve
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ getById: function (id) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "dataTypeApiBaseUrl",
+ "GetById",
+ [{ id: id }])),
+ "Failed to retrieve data for data type id " + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.dataTypeResource#getByName
+ * @methodOf umbraco.resources.dataTypeResource
+ *
+ * @description
+ * Gets a data type item with a given name
+ *
+ * ##usage
+ *
+ * @param {Object} args arguments object
+ * @param {Int} args.idd the ID of the node to move
+ * @param {Int} args.parentId the ID of the parent node to move to
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ move: function (args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.parentId) {
+ throw "args.parentId cannot be null";
+ }
+ if (!args.id) {
+ throw "args.id cannot be null";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"),
+ {
+ parentId: args.parentId,
+ id: args.id
+ }),
+ 'Failed to move content');
+ },
+
+ createContainer: function (parentId, name) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "dataTypeApiBaseUrl",
+ "PostCreateContainer",
+ { parentId: parentId, name: name })),
+ 'Failed to create a folder under parent id ' + parentId);
+ }
+ };
+}
+
+angular.module("umbraco.resources").factory("dataTypeResource", dataTypeResource);
+
+/**
+ * @ngdoc service
+ * @name umbraco.resources.entityResource
+ * @description Loads in basic data for all entities
+ *
+ * ##What is an entity?
+ * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most
+ * basic properties used to display the item in trees, lists and navigation.
+ *
+ * ##What is the difference between entity and content/media/etc...?
+ * the entity only contains the basic node data, name, id and guid, whereas content
+ * nodes fetched through the content service also contains additional all of the content property data, etc..
+ * This is the same principal for all entity types. Any user that is logged in to the back office will have access
+ * to view the basic entity information for all entities since the basic entity information does not contain sensitive information.
+ *
+ * ##Entity object types?
+ * You need to specify the type of object you want returned.
+ *
+ * The core object types are:
+ *
+ * - Document
+ * - Media
+ * - Member
+ * - Template
+ * - DocumentType
+ * - MediaType
+ * - MemberType
+ * - Macro
+ * - User
+ * - Language
+ * - Domain
+ * - DataType
+ **/
+function entityResource($q, $http, umbRequestHelper) {
+
+ //the factory object returned
+ return {
+
+ getSafeAlias: function (value, camelCase) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "entityApiBaseUrl",
+ "GetSafeAlias", { value: value, camelCase: camelCase })),
+ 'Failed to retrieve content type scaffold');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.entityResource#getPath
+ * @methodOf umbraco.resources.entityResource
+ *
+ * @description
+ * Returns a path, given a node ID and type
+ *
+ * ##usage
+ *
+ *
+ * @param {Int} id id of entity to return log history
+ * @returns {Promise} resourcePromise object containing the log.
+ *
+ */
+ getEntityLog: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "logApiBaseUrl",
+ "GetEntityLog",
+ [{ id: id }])),
+ 'Failed to retrieve user data for id ' + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.logResource#getUserLog
+ * @methodOf umbraco.resources.logResource
+ *
+ * @description
+ * Gets the current users' log history for a given type of log entry
+ *
+ * ##usage
+ *
+ *
+ * @param {String} type logtype to query for
+ * @param {DateTime} since query the log back to this date, by defalt 7 days ago
+ * @returns {Promise} resourcePromise object containing the log.
+ *
+ */
+ getUserLog: function (type, since) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "logApiBaseUrl",
+ "GetCurrentUserLog",
+ [{ logtype: type, sinceDate: since }])),
+ 'Failed to retrieve user data for id ' + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.logResource#getLog
+ * @methodOf umbraco.resources.logResource
+ *
+ * @description
+ * Gets the log history for a given type of log entry
+ *
+ * ##usage
+ *
+ *
+ * @param {Int} id id of media item to return
+ * @returns {Promise} resourcePromise object containing the media item.
+ *
+ */
+ getById: function (id) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "mediaApiBaseUrl",
+ "GetById",
+ [{ id: id }])),
+ 'Failed to retrieve data for media id ' + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.mediaResource#deleteById
+ * @methodOf umbraco.resources.mediaResource
+ *
+ * @description
+ * Deletes a media item with a given id
+ *
+ * ##usage
+ *
+ *
+ * @param {Array} ids ids of media items to return as an array
+ * @returns {Promise} resourcePromise object containing the media items array.
+ *
+ */
+ getByIds: function (ids) {
+
+ var idQuery = "";
+ _.each(ids, function (item) {
+ idQuery += "ids=" + item + "&";
+ });
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "mediaApiBaseUrl",
+ "GetByIds",
+ idQuery)),
+ 'Failed to retrieve data for media ids ' + ids);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.mediaResource#getScaffold
+ * @methodOf umbraco.resources.mediaResource
+ *
+ * @description
+ * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias.
+ *
+ * - Parent Id must be provided so umbraco knows where to store the media
+ * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold
+ *
+ * The scaffold is used to build editors for media that has not yet been populated with data.
+ *
+ * ##usage
+ *
+ * mediaResource.getScaffold(1234, 'folder')
+ * .then(function(scaffold) {
+ * var myDoc = scaffold;
+ * myDoc.name = "My new media item";
+ *
+ * mediaResource.save(myDoc, true)
+ * .then(function(media){
+ * alert("Retrieved, updated and saved again");
+ * });
+ * });
+ *
+ *
+ * @param {Int} parentId id of media item to return
+ * @param {String} alias mediatype alias to base the scaffold on
+ * @returns {Promise} resourcePromise object containing the media scaffold.
+ *
+ */
+ getScaffold: function (parentId, alias) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "mediaApiBaseUrl",
+ "GetEmpty",
+ [{ contentTypeAlias: alias }, { parentId: parentId }])),
+ 'Failed to retrieve data for empty media item type ' + alias);
+
+ },
+
+ rootMedia: function () {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "mediaApiBaseUrl",
+ "GetRootMedia")),
+ 'Failed to retrieve data for root media');
+
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.mediaResource#getChildren
+ * @methodOf umbraco.resources.mediaResource
+ *
+ * @description
+ * Gets children of a media item with a given id
+ *
+ * ##usage
+ *
+ * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+ * .then(function(contentArray) {
+ * var children = contentArray;
+ * alert('they are here!');
+ * });
+ *
+ *
+ * @param {Int} parentid id of content item to return children of
+ * @param {Object} options optional options object
+ * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0
+ * @param {Int} options.pageNumber if paging data, current page index, default = 0
+ * @param {String} options.filter if provided, query will only return those with names matching the filter
+ * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending`
+ * @param {String} options.orderBy property to order items by, default: `SortOrder`
+ * @returns {Promise} resourcePromise object containing an array of content items.
+ *
+ */
+ getChildren: function (parentId, options) {
+
+ var defaults = {
+ pageSize: 0,
+ pageNumber: 0,
+ filter: '',
+ orderDirection: "Ascending",
+ orderBy: "SortOrder",
+ orderBySystemField: true
+ };
+ if (options === undefined) {
+ options = {};
+ }
+ //overwrite the defaults if there are any specified
+ angular.extend(defaults, options);
+ //now copy back to the options we will use
+ options = defaults;
+ //change asc/desct
+ if (options.orderDirection === "asc") {
+ options.orderDirection = "Ascending";
+ }
+ else if (options.orderDirection === "desc") {
+ options.orderDirection = "Descending";
+ }
+
+ //converts the value to a js bool
+ function toBool(v) {
+ if (angular.isNumber(v)) {
+ return v > 0;
+ }
+ if (angular.isString(v)) {
+ return v === "true";
+ }
+ if (typeof v === "boolean") {
+ return v;
+ }
+ return false;
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "mediaApiBaseUrl",
+ "GetChildren",
+ [
+ { id: parentId },
+ { pageNumber: options.pageNumber },
+ { pageSize: options.pageSize },
+ { orderBy: options.orderBy },
+ { orderDirection: options.orderDirection },
+ { orderBySystemField: toBool(options.orderBySystemField) },
+ { filter: options.filter }
+ ])),
+ 'Failed to retrieve children for media item ' + parentId);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.mediaResource#save
+ * @methodOf umbraco.resources.mediaResource
+ *
+ * @description
+ * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation
+ * if the media item needs to have files attached, they must be provided as the files param and passed separately
+ *
+ *
+ * ##usage
+ *
+ * mediaResource.getById(1234)
+ * .then(function(media) {
+ * media.name = "I want a new name!";
+ * mediaResource.save(media, false)
+ * .then(function(media){
+ * alert("Retrieved, updated and saved again");
+ * });
+ * });
+ *
+ *
+ * @param {Object} media The media item object with changes applied
+ * @param {Bool} isNew set to true to create a new item or to update an existing
+ * @param {Array} files collection of files for the media item
+ * @returns {Promise} resourcePromise object containing the saved media item.
+ *
+ */
+ save: function (media, isNew, files) {
+ return saveMediaItem(media, "save" + (isNew ? "New" : ""), files);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.mediaResource#addFolder
+ * @methodOf umbraco.resources.mediaResource
+ *
+ * @description
+ * Shorthand for adding a media item of the type "Folder" under a given parent ID
+ *
+ * ##usage
+ *
+ *
+ * @param {string} name Name of the folder to create
+ * @param {int} parentId Id of the media item to create the folder underneath
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ addFolder: function (name, parentId) {
+ return umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper
+ .getApiUrl("mediaApiBaseUrl", "PostAddFolder"),
+ {
+ name: name,
+ parentId: parentId
+ }),
+ 'Failed to add folder');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.mediaResource#getChildFolders
+ * @methodOf umbraco.resources.mediaResource
+ *
+ * @description
+ * Retrieves all media children with types used as folders.
+ * Uses the convention of looking for media items with mediaTypes ending in
+ * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc,
+ *
+ * ##usage
+ *
+ *
+ * @param {Guid} key id of member item to delete
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ deleteByKey: function (key) {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "memberApiBaseUrl",
+ "DeleteByKey",
+ [{ key: key }])),
+ 'Failed to delete item ' + key);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.memberResource#getScaffold
+ * @methodOf umbraco.resources.memberResource
+ *
+ * @description
+ * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias.
+ *
+ * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold
+ *
+ * The scaffold is used to build editors for member that has not yet been populated with data.
+ *
+ * ##usage
+ *
+ * memberResource.getScaffold('client')
+ * .then(function(scaffold) {
+ * var myDoc = scaffold;
+ * myDoc.name = "My new member item";
+ *
+ * memberResource.save(myDoc, true)
+ * .then(function(member){
+ * alert("Retrieved, updated and saved again");
+ * });
+ * });
+ *
+ *
+ * @param {String} alias membertype alias to base the scaffold on
+ * @returns {Promise} resourcePromise object containing the member scaffold.
+ *
+ */
+ getScaffold: function (alias) {
+
+ if (alias) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "memberApiBaseUrl",
+ "GetEmpty",
+ [{ contentTypeAlias: alias }])),
+ 'Failed to retrieve data for empty member item type ' + alias);
+ }
+ else {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "memberApiBaseUrl",
+ "GetEmpty")),
+ 'Failed to retrieve data for empty member item type ' + alias);
+ }
+
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.memberResource#save
+ * @methodOf umbraco.resources.memberResource
+ *
+ * @description
+ * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation
+ * if the member needs to have files attached, they must be provided as the files param and passed separately
+ *
+ *
+ * ##usage
+ *
+ *
+ * @param {String} the unique package ID
+ * @returns {String} path to the downloaded zip file.
+ *
+ */
+ fetch: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "packageInstallApiBaseUrl",
+ "Fetch",
+ [{ packageGuid: id }])),
+ 'Failed to download package with guid ' + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.packageInstallResource#createmanifest
+ * @methodOf umbraco.resources.packageInstallResource
+ *
+ * @description
+ * Creates a package manifest for a given folder of files.
+ * This manifest keeps track of all installed files and data items
+ * so a package can be uninstalled at a later time.
+ * After creating a manifest, you can use the ID to install files and data.
+ *
+ * ##usage
+ *
+ *
+ * @returns {Promise} resourcePromise object containing the rules.
+ *
+ */
+ getRulesByName: function (name) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "stylesheetApiBaseUrl",
+ "GetRulesByName",
+ [{ name: name }])),
+ 'Failed to retrieve stylesheets ');
+ }
+ };
+}
+
+angular.module('umbraco.resources').factory('stylesheetResource', stylesheetResource);
+
+/**
+ * @ngdoc service
+ * @name umbraco.resources.treeResource
+ * @description Loads in data for trees
+ **/
+function treeResource($q, $http, umbRequestHelper) {
+
+ /** internal method to get the tree node's children url */
+ function getTreeNodesUrl(node) {
+ if (!node.childNodesUrl) {
+ throw "No childNodesUrl property found on the tree node, cannot load child nodes";
+ }
+ return node.childNodesUrl;
+ }
+
+ /** internal method to get the tree menu url */
+ function getTreeMenuUrl(node) {
+ if (!node.menuUrl) {
+ return null;
+ }
+ return node.menuUrl;
+ }
+
+ //the factory object returned
+ return {
+
+ /** Loads in the data to display the nodes menu */
+ loadMenu: function (node) {
+ var treeMenuUrl = getTreeMenuUrl(node);
+ if (treeMenuUrl !== undefined && treeMenuUrl !== null && treeMenuUrl.length > 0) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(getTreeMenuUrl(node)),
+ "Failed to retrieve data for a node's menu " + node.id);
+ } else {
+ return $q.reject({
+ errorMsg: "No tree menu url defined for node " + node.id
+ });
+ }
+ },
+
+ /** Loads in the data to display the nodes for an application */
+ loadApplication: function (options) {
+
+ if (!options || !options.section) {
+ throw "The object specified for does not contain a 'section' property";
+ }
+
+ if(!options.tree){
+ options.tree = "";
+ }
+ if (!options.isDialog) {
+ options.isDialog = false;
+ }
+
+ //create the query string for the tree request, these are the mandatory options:
+ var query = "application=" + options.section + "&tree=" + options.tree + "&isDialog=" + options.isDialog;
+
+ //the options can contain extra query string parameters
+ if (options.queryString) {
+ query += "&" + options.queryString;
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "treeApplicationApiBaseUrl",
+ "GetApplicationTrees",
+ query)),
+ 'Failed to retrieve data for application tree ' + options.section);
+ },
+
+ /** Loads in the data to display the child nodes for a given node */
+ loadNodes: function (options) {
+
+ if (!options || !options.node) {
+ throw "The options parameter object does not contain the required properties: 'node'";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(getTreeNodesUrl(options.node)),
+ 'Failed to retrieve data for child nodes ' + options.node.nodeId);
+ }
+ };
+}
+
+angular.module('umbraco.resources').factory('treeResource', treeResource);
+
+/**
+ * @ngdoc service
+ * @name umbraco.resources.userResource
+ **/
+function userResource($q, $http, umbDataFormatter, umbRequestHelper) {
+
+ return {
+
+ disableUser: function (userId) {
+
+ if (!userId) {
+ throw "userId not specified";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "userApiBaseUrl",
+ "PostDisableUser", [{ userId: userId }])),
+ 'Failed to disable the user ' + userId);
+ }
+ };
+}
+
+angular.module('umbraco.resources').factory('userResource', userResource);
+
+
+})();
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbraco.security.js b/WebCms/Umbraco/Js/umbraco.security.js
new file mode 100644
index 0000000..1d7f8cf
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.security.js
@@ -0,0 +1,206 @@
+/*! umbraco
+ * https://github.com/umbraco/umbraco-cms/
+ * Copyright (c) 2016 Umbraco HQ;
+ * Licensed
+ */
+
+(function() {
+
+//TODO: This is silly and unecessary to have a separate module for this
+angular.module('umbraco.security.retryQueue', []);
+angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']);
+angular.module('umbraco.security', ['umbraco.security.retryQueue', 'umbraco.security.interceptor']);
+//TODO: This is silly and unecessary to have a separate module for this
+angular.module('umbraco.security.retryQueue', [])
+
+// This is a generic retry queue for security failures. Each item is expected to expose two functions: retry and cancel.
+.factory('securityRetryQueue', ['$q', '$log', function ($q, $log) {
+
+ var retryQueue = [];
+ var retryUser = null;
+
+ var service = {
+ // The security service puts its own handler in here!
+ onItemAddedCallbacks: [],
+
+ hasMore: function() {
+ return retryQueue.length > 0;
+ },
+ push: function(retryItem) {
+ retryQueue.push(retryItem);
+ // Call all the onItemAdded callbacks
+ angular.forEach(service.onItemAddedCallbacks, function(cb) {
+ try {
+ cb(retryItem);
+ } catch(e) {
+ $log.error('securityRetryQueue.push(retryItem): callback threw an error' + e);
+ }
+ });
+ },
+ pushRetryFn: function(reason, userName, retryFn) {
+ // The reason parameter is optional
+ if ( arguments.length === 2) {
+ retryFn = userName;
+ userName = reason;
+ reason = undefined;
+ }
+
+ if ((retryUser && retryUser !== userName) || userName === null) {
+ throw new Error('invalid user');
+ }
+
+ retryUser = userName;
+
+ // The deferred object that will be resolved or rejected by calling retry or cancel
+ var deferred = $q.defer();
+ var retryItem = {
+ reason: reason,
+ retry: function() {
+ // Wrap the result of the retryFn into a promise if it is not already
+ $q.when(retryFn()).then(function(value) {
+ // If it was successful then resolve our deferred
+ deferred.resolve(value);
+ }, function(value) {
+ // Othewise reject it
+ deferred.reject(value);
+ });
+ },
+ cancel: function() {
+ // Give up on retrying and reject our deferred
+ deferred.reject();
+ }
+ };
+ service.push(retryItem);
+ return deferred.promise;
+ },
+ retryReason: function() {
+ return service.hasMore() && retryQueue[0].reason;
+ },
+ cancelAll: function() {
+ while(service.hasMore()) {
+ retryQueue.shift().cancel();
+ }
+ retryUser = null;
+ },
+ retryAll: function (userName) {
+
+ if (retryUser == null) {
+ return;
+ }
+
+ if (retryUser !== userName) {
+ service.cancelAll();
+ return;
+ }
+
+ while(service.hasMore()) {
+ retryQueue.shift().retry();
+ }
+ }
+ };
+ return service;
+}]);
+angular.module('umbraco.security.interceptor')
+ // This http interceptor listens for authentication successes and failures
+ .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) {
+ return function(promise) {
+
+ return promise.then(
+ function(originalResponse) {
+ // Intercept successful requests
+
+ //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it
+ //expires. Then we'll update the user in the user service accordingly.
+ var headers = originalResponse.headers();
+ if (headers["x-umb-user-seconds"]) {
+ // We must use $injector to get the $http service to prevent circular dependency
+ var userService = $injector.get('userService');
+ userService.setUserTimeout(headers["x-umb-user-seconds"]);
+ }
+
+ return promise;
+ }, function(originalResponse) {
+ // Intercept failed requests
+
+ //Here we'll check if we should ignore the error, this will be based on an original header set
+ var headers = originalResponse.config ? originalResponse.config.headers : {};
+ if (headers["x-umb-ignore-error"] === "ignore") {
+ //exit/ignore
+ return promise;
+ }
+ var filtered = _.find(requestInterceptorFilter(), function(val) {
+ return originalResponse.config.url.indexOf(val) > 0;
+ });
+ if (filtered) {
+ return promise;
+ }
+
+ //A 401 means that the user is not logged in
+ if (originalResponse.status === 401) {
+
+ var userService = $injector.get('userService'); // see above
+
+ //Associate the user name with the retry to ensure we retry for the right user
+ promise = userService.getCurrentUser()
+ .then(function (user) {
+ var userName = user ? user.name : null;
+ //The request bounced because it was not authorized - add a new request to the retry queue
+ return queue.pushRetryFn('unauthorized-server', userName, function retryRequest() {
+ // We must use $injector to get the $http service to prevent circular dependency
+ return $injector.get('$http')(originalResponse.config);
+ });
+ });
+ }
+ else if (originalResponse.status === 404) {
+
+ //a 404 indicates that the request was not found - this could be due to a non existing url, or it could
+ //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it
+
+ var errMsg = "The URL returned a 404 (not found): " + originalResponse.config.url.split('?')[0] + "";
+ if (originalResponse.data && originalResponse.data.ExceptionMessage) {
+ errMsg += " with error: " + originalResponse.data.ExceptionMessage + "";
+ }
+ if (originalResponse.config.data) {
+ errMsg += " with data: " + angular.toJson(originalResponse.config.data) + " Contact your administrator for information.";
+ }
+
+ notifications.error(
+ "Request error",
+ errMsg);
+
+ }
+ else if (originalResponse.status === 403) {
+ //if the status was a 403 it means the user didn't have permission to do what the request was trying to do.
+ //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was
+ //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper,
+ // or completely custom for services calling resources.
+
+ //http://issues.umbraco.org/issue/U4-2749
+
+ //It was decided to just put these messages into the normal status messages.
+
+ var msg = "Unauthorized access to URL: " + originalResponse.config.url.split('?')[0] + "";
+ if (originalResponse.config.data) {
+ msg += " with data: " + angular.toJson(originalResponse.config.data) + " Contact your administrator for information.";
+ }
+
+ notifications.error(
+ "Authorization error",
+ msg);
+ }
+
+ return promise;
+ });
+ };
+ }])
+
+ .value('requestInterceptorFilter', function() {
+ return ["www.gravatar.com"];
+ })
+
+ // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.
+ .config(['$httpProvider', function ($httpProvider) {
+ $httpProvider.responseInterceptors.push('securityInterceptor');
+ }]);
+
+})();
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbraco.servervariables.js b/WebCms/Umbraco/Js/umbraco.servervariables.js
new file mode 100644
index 0000000..7ba1448
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.servervariables.js
@@ -0,0 +1,42 @@
+//create the namespace (NOTE: This loads before any dependencies so we don't have a namespace mgr so we just create it manually)
+var Umbraco = {};
+Umbraco.Sys = {};
+//define a global static object
+Umbraco.Sys.ServerVariables = {
+ umbracoUrls: {
+ "contentApiBaseUrl": "/umbraco/UmbracoApi/Content/",
+ "mediaApiBaseUrl": "/umbraco/UmbracoApi/Media/",
+ "dataTypeApiBaseUrl": "/umbraco/UmbracoApi/DataType/",
+ "sectionApiBaseUrl": "/umbraco/UmbracoApi/Section/",
+ "treeApplicationApiBaseUrl": "/umbraco/UmbracoTrees/ApplicationTreeApi/",
+ "contentTypeApiBaseUrl": "/umbraco/Api/ContentType/",
+ "mediaTypeApiBaseUrl": "/umbraco/Api/MediaType/",
+ "macroApiBaseUrl": "/umbraco/Api/Macro/",
+ "authenticationApiBaseUrl": "/umbraco/UmbracoApi/Authentication/",
+ //For this we'll just provide a file that exists during the mock session since we don't really have legay js tree stuff
+ "legacyTreeJs": "/belle/lib/lazyload/empty.js",
+ "serverVarsJs": "/belle/lib/lazyload/empty.js",
+ "imagesApiBaseUrl": "/umbraco/UmbracoApi/Images/",
+ "entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/",
+ "dashboardApiBaseUrl": "/umbraco/UmbracoApi/Dashboard/",
+ "updateCheckApiBaseUrl": "/umbraco/Api/UpdateCheck/",
+ "relationApiBaseUrl": "/umbraco/UmbracoApi/Relation/",
+ "rteApiBaseUrl": "/umbraco/UmbracoApi/RichTextPreValue/"
+ },
+ umbracoSettings: {
+ "umbracoPath": "/umbraco",
+ "appPluginsPath" : "/App_Plugins",
+ "imageFileTypes": "jpeg,jpg,gif,bmp,png,tiff,tif",
+ "keepUserLoggedIn": true
+ },
+ umbracoPlugins: {
+ trees: [
+ { alias: "myTree", packageFolder: "MyPackage" }
+ ]
+ },
+ isDebuggingEnabled: true,
+ application: {
+ assemblyVersion: "1",
+ version: "7"
+ }
+};
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbraco.services.js b/WebCms/Umbraco/Js/umbraco.services.js
new file mode 100644
index 0000000..13b5cd5
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.services.js
@@ -0,0 +1,9984 @@
+/*! umbraco
+ * https://github.com/umbraco/umbraco-cms/
+ * Copyright (c) 2016 Umbraco HQ;
+ * Licensed
+ */
+
+(function() {
+
+angular.module("umbraco.services", ["umbraco.security", "umbraco.resources"]);
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.angularHelper
+ * @function
+ *
+ * @description
+ * Some angular helper/extension methods
+ */
+function angularHelper($log, $q) {
+ return {
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#rejectedPromise
+ * @methodOf umbraco.services.angularHelper
+ * @function
+ *
+ * @description
+ * In some situations we need to return a promise as a rejection, normally based on invalid data. This
+ * is a wrapper to do that so we can save on writing a bit of code.
+ *
+ * @param {object} objReject The object to send back with the promise rejection
+ */
+ rejectedPromise: function (objReject) {
+ var deferred = $q.defer();
+ //return an error object including the error message for UI
+ deferred.reject(objReject);
+ return deferred.promise;
+ },
+
+ /**
+ * @ngdoc function
+ * @name safeApply
+ * @methodOf umbraco.services.angularHelper
+ * @function
+ *
+ * @description
+ * This checks if a digest/apply is already occuring, if not it will force an apply call
+ */
+ safeApply: function (scope, fn) {
+ if (scope.$$phase || scope.$root.$$phase) {
+ if (angular.isFunction(fn)) {
+ fn();
+ }
+ }
+ else {
+ if (angular.isFunction(fn)) {
+ scope.$apply(fn);
+ }
+ else {
+ scope.$apply();
+ }
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name getCurrentForm
+ * @methodOf umbraco.services.angularHelper
+ * @function
+ *
+ * @description
+ * Returns the current form object applied to the scope or null if one is not found
+ */
+ getCurrentForm: function (scope) {
+
+ //NOTE: There isn't a way in angular to get a reference to the current form object since the form object
+ // is just defined as a property of the scope when it is named but you'll always need to know the name which
+ // isn't very convenient. If we want to watch for validation changes we need to get a form reference.
+ // The way that we detect the form object is a bit hackerific in that we detect all of the required properties
+ // that exist on a form object.
+ //
+ //The other way to do it in a directive is to require "^form", but in a controller the only other way to do it
+ // is to inject the $element object and use: $element.inheritedData('$formController');
+
+ var form = null;
+ //var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$invalid", "$addControl", "$removeControl", "$setValidity", "$setDirty"];
+ var requiredFormProps = ["$addControl", "$removeControl", "$setValidity", "$setDirty", "$setPristine"];
+
+ // a method to check that the collection of object prop names contains the property name expected
+ function propertyExists(objectPropNames) {
+ //ensure that every required property name exists on the current scope property
+ return _.every(requiredFormProps, function (item) {
+
+ return _.contains(objectPropNames, item);
+ });
+ }
+
+ for (var p in scope) {
+
+ if (_.isObject(scope[p]) && p !== "this" && p.substr(0, 1) !== "$") {
+ //get the keys of the property names for the current property
+ var props = _.keys(scope[p]);
+ //if the length isn't correct, try the next prop
+ if (props.length < requiredFormProps.length) {
+ continue;
+ }
+
+ //ensure that every required property name exists on the current scope property
+ var containProperty = propertyExists(props);
+
+ if (containProperty) {
+ form = scope[p];
+ break;
+ }
+ }
+ }
+
+ return form;
+ },
+
+ /**
+ * @ngdoc function
+ * @name validateHasForm
+ * @methodOf umbraco.services.angularHelper
+ * @function
+ *
+ * @description
+ * This will validate that the current scope has an assigned form object, if it doesn't an exception is thrown, if
+ * it does we return the form object.
+ */
+ getRequiredCurrentForm: function (scope) {
+ var currentForm = this.getCurrentForm(scope);
+ if (!currentForm || !currentForm.$name) {
+ throw "The current scope requires a current form object (or ng-form) with a name assigned to it";
+ }
+ return currentForm;
+ },
+
+ /**
+ * @ngdoc function
+ * @name getNullForm
+ * @methodOf umbraco.services.angularHelper
+ * @function
+ *
+ * @description
+ * Returns a null angular FormController, mostly for use in unit tests
+ * NOTE: This is actually the same construct as angular uses internally for creating a null form but they don't expose
+ * any of this publicly to us, so we need to create our own.
+ *
+ * @param {string} formName The form name to assign
+ */
+ getNullForm: function (formName) {
+ return {
+ $addControl: angular.noop,
+ $removeControl: angular.noop,
+ $setValidity: angular.noop,
+ $setDirty: angular.noop,
+ $setPristine: angular.noop,
+ $name: formName
+ //NOTE: we don't include the 'properties', just the methods.
+ };
+ }
+ };
+}
+angular.module('umbraco.services').factory('angularHelper', angularHelper);
+/**
+ * @ngdoc service
+ * @name umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Tracks the various application state variables when working in the back office, raises events when state changes.
+ *
+ * ##Samples
+ *
+ * ####Subscribe to global state changes:
+ *
+ *
+ */
+function appState(eventsService) {
+
+ //Define all variables here - we are never returning this objects so they cannot be publicly mutable
+ // changed, we only expose methods to interact with the values.
+
+ var globalState = {
+ showNavigation: null,
+ touchDevice: null,
+ showTray: null,
+ stickyNavigation: null,
+ navMode: null,
+ isReady: null,
+ isTablet: null
+ };
+
+ var sectionState = {
+ //The currently active section
+ currentSection: null,
+ showSearchResults: null
+ };
+
+ var treeState = {
+ //The currently selected node
+ selectedNode: null,
+ //The currently loaded root node reference - depending on the section loaded this could be a section root or a normal root.
+ //We keep this reference so we can lookup nodes to interact with in the UI via the tree service
+ currentRootNode: null
+ };
+
+ var menuState = {
+ //this list of menu items to display
+ menuActions: null,
+ //the title to display in the context menu dialog
+ dialogTitle: null,
+ //The tree node that the ctx menu is launched for
+ currentNode: null,
+ //Whether the menu's dialog is being shown or not
+ showMenuDialog: null,
+ //Whether the context menu is being shown or not
+ showMenu: null
+ };
+
+ /** function to validate and set the state on a state object */
+ function setState(stateObj, key, value, stateObjName) {
+ if (!_.has(stateObj, key)) {
+ throw "The variable " + key + " does not exist in " + stateObjName;
+ }
+ var changed = stateObj[key] !== value;
+ stateObj[key] = value;
+ if (changed) {
+ eventsService.emit("appState." + stateObjName + ".changed", { key: key, value: value });
+ }
+ }
+
+ /** function to validate and set the state on a state object */
+ function getState(stateObj, key, stateObjName) {
+ if (!_.has(stateObj, key)) {
+ throw "The variable " + key + " does not exist in " + stateObjName;
+ }
+ return stateObj[key];
+ }
+
+ return {
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#getGlobalState
+ * @methodOf umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Returns the current global state value by key - we do not return an object reference here - we do NOT want this
+ * to be publicly mutable and allow setting arbitrary values
+ *
+ */
+ getGlobalState: function (key) {
+ return getState(globalState, key, "globalState");
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#setGlobalState
+ * @methodOf umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Sets a global state value by key
+ *
+ */
+ setGlobalState: function (key, value) {
+ setState(globalState, key, value, "globalState");
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#getSectionState
+ * @methodOf umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Returns the current section state value by key - we do not return an object here - we do NOT want this
+ * to be publicly mutable and allow setting arbitrary values
+ *
+ */
+ getSectionState: function (key) {
+ return getState(sectionState, key, "sectionState");
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#setSectionState
+ * @methodOf umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Sets a section state value by key
+ *
+ */
+ setSectionState: function(key, value) {
+ setState(sectionState, key, value, "sectionState");
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#getTreeState
+ * @methodOf umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Returns the current tree state value by key - we do not return an object here - we do NOT want this
+ * to be publicly mutable and allow setting arbitrary values
+ *
+ */
+ getTreeState: function (key) {
+ return getState(treeState, key, "treeState");
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#setTreeState
+ * @methodOf umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Sets a section state value by key
+ *
+ */
+ setTreeState: function (key, value) {
+ setState(treeState, key, value, "treeState");
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#getMenuState
+ * @methodOf umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Returns the current menu state value by key - we do not return an object here - we do NOT want this
+ * to be publicly mutable and allow setting arbitrary values
+ *
+ */
+ getMenuState: function (key) {
+ return getState(menuState, key, "menuState");
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#setMenuState
+ * @methodOf umbraco.services.appState
+ * @function
+ *
+ * @description
+ * Sets a section state value by key
+ *
+ */
+ setMenuState: function (key, value) {
+ setState(menuState, key, value, "menuState");
+ },
+
+ };
+}
+angular.module('umbraco.services').factory('appState', appState);
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.editorState
+ * @function
+ *
+ * @description
+ * Tracks the parent object for complex editors by exposing it as
+ * an object reference via editorState.current.entity
+ *
+ * it is possible to modify this object, so should be used with care
+ */
+angular.module('umbraco.services').factory("editorState", function() {
+
+ var current = null;
+ var state = {
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#set
+ * @methodOf umbraco.services.editorState
+ * @function
+ *
+ * @description
+ * Sets the current entity object for the currently active editor
+ * This is only used when implementing an editor with a complex model
+ * like the content editor, where the model is modified by several
+ * child controllers.
+ */
+ set: function (entity) {
+ current = entity;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#reset
+ * @methodOf umbraco.services.editorState
+ * @function
+ *
+ * @description
+ * Since the editorstate entity is read-only, you cannot set it to null
+ * only through the reset() method
+ */
+ reset: function() {
+ current = null;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.angularHelper#getCurrent
+ * @methodOf umbraco.services.editorState
+ * @function
+ *
+ * @description
+ * Returns an object reference to the current editor entity.
+ * the entity is the root object of the editor.
+ * EditorState is used by property/parameter editors that need
+ * access to the entire entity being edited, not just the property/parameter
+ *
+ * editorState.current can not be overwritten, you should only read values from it
+ * since modifying individual properties should be handled by the property editors
+ */
+ getCurrent: function() {
+ return current;
+ }
+ };
+
+ //TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing.
+
+ //create a get/set property but don't allow setting
+ Object.defineProperty(state, "current", {
+ get: function () {
+ return current;
+ },
+ set: function (value) {
+ throw "Use editorState.set to set the value of the current entity";
+ },
+ });
+
+ return state;
+});
+/**
+ * @ngdoc service
+ * @name umbraco.services.assetsService
+ *
+ * @requires $q
+ * @requires angularHelper
+ *
+ * @description
+ * Promise-based utillity service to lazy-load client-side dependencies inside angular controllers.
+ *
+ * ##usage
+ * To use, simply inject the assetsService into any controller that needs it, and make
+ * sure the umbraco.services module is accesible - which it should be by default.
+ *
+ *
+ * angular.module("umbraco").controller("my.controller". function(assetsService){
+ * assetsService.load(["script.js", "styles.css"], $scope).then(function(){
+ * //this code executes when the dependencies are done loading
+ * });
+ * });
+ *
+ *
+ * You can also load individual files, which gives you greater control over what attibutes are passed to the file, as well as timeout
+ *
+ *
+ *
+ * For these cases, there are 2 individual methods, one for javascript, and one for stylesheets:
+ *
+ *
+ * angular.module("umbraco").controller("my.controller". function(assetsService){
+ * assetsService.loadCss("stye.css", $scope, {media: 'print'}, 10000 }).then(function(){
+ * //loadcss cannot determine when the css is done loading, so this will trigger instantly
+ * });
+ * });
+ *
+ */
+angular.module('umbraco.services')
+.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http) {
+
+ var initAssetsLoaded = false;
+ var appendRnd = function (url) {
+ //if we don't have a global umbraco obj yet, the app is bootstrapping
+ if (!Umbraco.Sys.ServerVariables.application) {
+ return url;
+ }
+
+ var rnd = Umbraco.Sys.ServerVariables.application.version + "." + Umbraco.Sys.ServerVariables.application.cdf;
+ var _op = (url.indexOf("?") > 0) ? "&" : "?";
+ url = url + _op + "umb__rnd=" + rnd;
+ return url;
+ };
+
+ function convertVirtualPath(path) {
+ //make this work for virtual paths
+ if (path.startsWith("~/")) {
+ path = umbRequestHelper.convertVirtualToAbsolutePath(path);
+ }
+ return path;
+ }
+
+ var service = {
+ loadedAssets: {},
+
+ _getAssetPromise: function (path) {
+
+ if (this.loadedAssets[path]) {
+ return this.loadedAssets[path];
+ } else {
+ var deferred = $q.defer();
+ this.loadedAssets[path] = { deferred: deferred, state: "new", path: path };
+ return this.loadedAssets[path];
+ }
+ },
+ /**
+ Internal method. This is called when the application is loading and the user is already authenticated, or once the user is authenticated.
+ There's a few assets the need to be loaded for the application to function but these assets require authentication to load.
+ */
+ _loadInitAssets: function () {
+ var deferred = $q.defer();
+ //here we need to ensure the required application assets are loaded
+ if (initAssetsLoaded === false) {
+ var self = this;
+ self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () {
+ initAssetsLoaded = true;
+
+ //now we need to go get the legacyTreeJs - but this can be done async without waiting.
+ self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope);
+
+ deferred.resolve();
+ });
+ }
+ else {
+ deferred.resolve();
+ }
+ return deferred.promise;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.assetsService#loadCss
+ * @methodOf umbraco.services.assetsService
+ *
+ * @description
+ * Injects a file as a stylesheet into the document head
+ *
+ * @param {String} path path to the css file to load
+ * @param {Scope} scope optional scope to pass into the loader
+ * @param {Object} keyvalue collection of attributes to pass to the stylesheet element
+ * @param {Number} timeout in milliseconds
+ * @returns {Promise} Promise object which resolves when the file has loaded
+ */
+ loadCss: function (path, scope, attributes, timeout) {
+
+ path = convertVirtualPath(path);
+
+ var asset = this._getAssetPromise(path); // $q.defer();
+ var t = timeout || 5000;
+ var a = attributes || undefined;
+
+ if (asset.state === "new") {
+ asset.state = "loading";
+ LazyLoad.css(appendRnd(path), function () {
+ if (!scope) {
+ asset.state = "loaded";
+ asset.deferred.resolve(true);
+ } else {
+ asset.state = "loaded";
+ angularHelper.safeApply(scope, function () {
+ asset.deferred.resolve(true);
+ });
+ }
+ });
+ } else if (asset.state === "loaded") {
+ asset.deferred.resolve(true);
+ }
+ return asset.deferred.promise;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.assetsService#loadJs
+ * @methodOf umbraco.services.assetsService
+ *
+ * @description
+ * Injects a file as a javascript into the document
+ *
+ * @param {String} path path to the js file to load
+ * @param {Scope} scope optional scope to pass into the loader
+ * @param {Object} keyvalue collection of attributes to pass to the script element
+ * @param {Number} timeout in milliseconds
+ * @returns {Promise} Promise object which resolves when the file has loaded
+ */
+ loadJs: function (path, scope, attributes, timeout) {
+
+ path = convertVirtualPath(path);
+
+ var asset = this._getAssetPromise(path); // $q.defer();
+ var t = timeout || 5000;
+ var a = attributes || undefined;
+
+ if (asset.state === "new") {
+ asset.state = "loading";
+
+ LazyLoad.js(appendRnd(path), function () {
+ if (!scope) {
+ asset.state = "loaded";
+ asset.deferred.resolve(true);
+ } else {
+ asset.state = "loaded";
+ angularHelper.safeApply(scope, function () {
+ asset.deferred.resolve(true);
+ });
+ }
+ });
+
+ } else if (asset.state === "loaded") {
+ asset.deferred.resolve(true);
+ }
+
+ return asset.deferred.promise;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.assetsService#load
+ * @methodOf umbraco.services.assetsService
+ *
+ * @description
+ * Injects a collection of files, this can be ONLY js files
+ *
+ *
+ * @param {Array} pathArray string array of paths to the files to load
+ * @param {Scope} scope optional scope to pass into the loader
+ * @returns {Promise} Promise object which resolves when all the files has loaded
+ */
+ load: function (pathArray, scope) {
+ var promise;
+
+ if (!angular.isArray(pathArray)) {
+ throw "pathArray must be an array";
+ }
+
+ var nonEmpty = _.reject(pathArray, function (item) {
+ return item === undefined || item === "";
+ });
+
+
+ //don't load anything if there's nothing to load
+ if (nonEmpty.length > 0) {
+ var promises = [];
+ var assets = [];
+
+ //compile a list of promises
+ //blocking
+ _.each(nonEmpty, function (path) {
+
+ path = convertVirtualPath(path);
+
+ var asset = service._getAssetPromise(path);
+ //if not previously loaded, add to list of promises
+ if (asset.state !== "loaded") {
+ if (asset.state === "new") {
+ asset.state = "loading";
+ assets.push(asset);
+ }
+
+ //we need to always push to the promises collection to monitor correct
+ //execution
+ promises.push(asset.deferred.promise);
+ }
+ });
+
+
+ //gives a central monitoring of all assets to load
+ promise = $q.all(promises);
+
+ _.each(assets, function (asset) {
+ LazyLoad.js(appendRnd(asset.path), function () {
+ asset.state = "loaded";
+ if (!scope) {
+ asset.deferred.resolve(true);
+ }
+ else {
+ angularHelper.safeApply(scope, function () {
+ asset.deferred.resolve(true);
+ });
+ }
+ });
+ });
+ }
+ else {
+ //return and resolve
+ var deferred = $q.defer();
+ promise = deferred.promise;
+ deferred.resolve(true);
+ }
+
+
+ return promise;
+ }
+ };
+
+ return service;
+});
+
+/**
+* @ngdoc service
+* @name umbraco.services.contentEditingHelper
+* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by
+* all editors to share logic and reduce the amount of replicated code among editors.
+**/
+function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper, appState) {
+
+ function isValidIdentifier(id){
+ //empty id <= 0
+ if(angular.isNumber(id) && id > 0){
+ return true;
+ }
+
+ //empty guid
+ if(id === "00000000-0000-0000-0000-000000000000"){
+ return false;
+ }
+
+ //empty string / alias
+ if(id === ""){
+ return false;
+ }
+
+ return true;
+ }
+
+ return {
+
+ /** Used by the content editor and mini content editor to perform saving operations */
+ contentEditorPerformSave: function (args) {
+ if (!angular.isObject(args)) {
+ throw "args must be an object";
+ }
+ if (!args.scope) {
+ throw "args.scope is not defined";
+ }
+ if (!args.content) {
+ throw "args.content is not defined";
+ }
+ if (!args.statusMessage) {
+ throw "args.statusMessage is not defined";
+ }
+ if (!args.saveMethod) {
+ throw "args.saveMethod is not defined";
+ }
+
+ var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true;
+
+ var self = this;
+
+ //we will use the default one for content if not specified
+ var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback;
+
+ var deferred = $q.defer();
+
+ if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage, action: args.action })) {
+
+ args.scope.busy = true;
+
+ args.saveMethod(args.content, $routeParams.create, fileManager.getFiles())
+ .then(function (data) {
+
+ formHelper.resetForm({ scope: args.scope, notifications: data.notifications });
+
+ self.handleSuccessfulSave({
+ scope: args.scope,
+ savedContent: data,
+ rebindCallback: function() {
+ rebindCallback.apply(self, [args.content, data]);
+ }
+ });
+
+ args.scope.busy = false;
+ deferred.resolve(data);
+
+ }, function (err) {
+ self.handleSaveError({
+ redirectOnFailure: redirectOnFailure,
+ err: err,
+ rebindCallback: function() {
+ rebindCallback.apply(self, [args.content, err.data]);
+ }
+ });
+ //show any notifications
+ if (angular.isArray(err.data.notifications)) {
+ for (var i = 0; i < err.data.notifications.length; i++) {
+ notificationsService.showNotification(err.data.notifications[i]);
+ }
+ }
+ args.scope.busy = false;
+ deferred.reject(err);
+ });
+ }
+ else {
+ deferred.reject();
+ }
+
+ return deferred.promise;
+ },
+
+
+ /** Returns the action button definitions based on what permissions the user has.
+ The content.allowedActions parameter contains a list of chars, each represents a button by permission so
+ here we'll build the buttons according to the chars of the user. */
+ configureContentEditorButtons: function (args) {
+
+ if (!angular.isObject(args)) {
+ throw "args must be an object";
+ }
+ if (!args.content) {
+ throw "args.content is not defined";
+ }
+ if (!args.methods) {
+ throw "args.methods is not defined";
+ }
+ if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.save || !args.methods.unPublish) {
+ throw "args.methods does not contain all required defined methods";
+ }
+
+ var buttons = {
+ defaultButton: null,
+ subButtons: []
+ };
+
+ function createButtonDefinition(ch) {
+ switch (ch) {
+ case "U":
+ //publish action
+ return {
+ letter: ch,
+ labelKey: "buttons_saveAndPublish",
+ handler: args.methods.saveAndPublish,
+ hotKey: "ctrl+p",
+ hotKeyWhenHidden: true
+ };
+ case "H":
+ //send to publish
+ return {
+ letter: ch,
+ labelKey: "buttons_saveToPublish",
+ handler: args.methods.sendToPublish,
+ hotKey: "ctrl+p",
+ hotKeyWhenHidden: true
+ };
+ case "A":
+ //save
+ return {
+ letter: ch,
+ labelKey: "buttons_save",
+ handler: args.methods.save,
+ hotKey: "ctrl+s",
+ hotKeyWhenHidden: true
+ };
+ case "Z":
+ //unpublish
+ return {
+ letter: ch,
+ labelKey: "content_unPublish",
+ handler: args.methods.unPublish,
+ hotKey: "ctrl+u",
+ hotKeyWhenHidden: true
+ };
+ default:
+ return null;
+ }
+ }
+
+ //reset
+ buttons.subButtons = [];
+
+ //This is the ideal button order but depends on circumstance, we'll use this array to create the button list
+ // Publish, SendToPublish, Save
+ var buttonOrder = ["U", "H", "A"];
+
+ //Create the first button (primary button)
+ //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item.
+ if (!args.create || _.contains(args.content.allowedActions, "C")) {
+ for (var b in buttonOrder) {
+ if (_.contains(args.content.allowedActions, buttonOrder[b])) {
+ buttons.defaultButton = createButtonDefinition(buttonOrder[b]);
+ break;
+ }
+ }
+ }
+
+ //Now we need to make the drop down button list, this is also slightly tricky because:
+ //We cannot have any buttons if there's no default button above.
+ //We cannot have the unpublish button (Z) when there's no publish permission.
+ //We cannot have the unpublish button (Z) when the item is not published.
+ if (buttons.defaultButton) {
+
+ //get the last index of the button order
+ var lastIndex = _.indexOf(buttonOrder, buttons.defaultButton.letter);
+ //add the remaining
+ for (var i = lastIndex + 1; i < buttonOrder.length; i++) {
+ if (_.contains(args.content.allowedActions, buttonOrder[i])) {
+ buttons.subButtons.push(createButtonDefinition(buttonOrder[i]));
+ }
+ }
+
+
+ //if we are not creating, then we should add unpublish too,
+ // so long as it's already published and if the user has access to publish
+ if (!args.create) {
+ if (args.content.publishDate && _.contains(args.content.allowedActions, "U")) {
+ buttons.subButtons.push(createButtonDefinition("Z"));
+ }
+ }
+ }
+
+ return buttons;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.contentEditingHelper#getAllProps
+ * @methodOf umbraco.services.contentEditingHelper
+ * @function
+ *
+ * @description
+ * Returns all propertes contained for the content item (since the normal model has properties contained inside of tabs)
+ */
+ getAllProps: function (content) {
+ var allProps = [];
+
+ for (var i = 0; i < content.tabs.length; i++) {
+ for (var p = 0; p < content.tabs[i].properties.length; p++) {
+ allProps.push(content.tabs[i].properties[p]);
+ }
+ }
+
+ return allProps;
+ },
+
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.contentEditingHelper#configureButtons
+ * @methodOf umbraco.services.contentEditingHelper
+ * @function
+ *
+ * @description
+ * Returns a letter array for buttons, with the primary one first based on content model, permissions and editor state
+ */
+ getAllowedActions : function(content, creating){
+
+ //This is the ideal button order but depends on circumstance, we'll use this array to create the button list
+ // Publish, SendToPublish, Save
+ var actionOrder = ["U", "H", "A"];
+ var defaultActions;
+ var actions = [];
+
+ //Create the first button (primary button)
+ //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item.
+ if (!creating || _.contains(content.allowedActions, "C")) {
+ for (var b in actionOrder) {
+ if (_.contains(content.allowedActions, actionOrder[b])) {
+ defaultAction = actionOrder[b];
+ break;
+ }
+ }
+ }
+
+ actions.push(defaultAction);
+
+ //Now we need to make the drop down button list, this is also slightly tricky because:
+ //We cannot have any buttons if there's no default button above.
+ //We cannot have the unpublish button (Z) when there's no publish permission.
+ //We cannot have the unpublish button (Z) when the item is not published.
+ if (defaultAction) {
+ //get the last index of the button order
+ var lastIndex = _.indexOf(actionOrder, defaultAction);
+
+ //add the remaining
+ for (var i = lastIndex + 1; i < actionOrder.length; i++) {
+ if (_.contains(content.allowedActions, actionOrder[i])) {
+ actions.push(actionOrder[i]);
+ }
+ }
+
+ //if we are not creating, then we should add unpublish too,
+ // so long as it's already published and if the user has access to publish
+ if (!creating) {
+ if (content.publishDate && _.contains(content.allowedActions,"U")) {
+ actions.push("Z");
+ }
+ }
+ }
+
+ return actions;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.contentEditingHelper#getButtonFromAction
+ * @methodOf umbraco.services.contentEditingHelper
+ * @function
+ *
+ * @description
+ * Returns a button object to render a button for the tabbed editor
+ * currently only returns built in system buttons for content and media actions
+ * returns label, alias, action char and hot-key
+ */
+ getButtonFromAction : function(ch){
+ switch (ch) {
+ case "U":
+ return {
+ letter: ch,
+ labelKey: "buttons_saveAndPublish",
+ handler: "saveAndPublish",
+ hotKey: "ctrl+p"
+ };
+ case "H":
+ //send to publish
+ return {
+ letter: ch,
+ labelKey: "buttons_saveToPublish",
+ handler: "sendToPublish",
+ hotKey: "ctrl+p"
+ };
+ case "A":
+ return {
+ letter: ch,
+ labelKey: "buttons_save",
+ handler: "save",
+ hotKey: "ctrl+s"
+ };
+ case "Z":
+ return {
+ letter: ch,
+ labelKey: "content_unPublish",
+ handler: "unPublish"
+ };
+
+ default:
+ return null;
+ }
+
+ },
+ /**
+ * @ngdoc method
+ * @name umbraco.services.contentEditingHelper#reBindChangedProperties
+ * @methodOf umbraco.services.contentEditingHelper
+ * @function
+ *
+ * @description
+ * re-binds all changed property values to the origContent object from the savedContent object and returns an array of changed properties.
+ */
+ reBindChangedProperties: function (origContent, savedContent) {
+
+ var changed = [];
+
+ //get a list of properties since they are contained in tabs
+ var allOrigProps = this.getAllProps(origContent);
+ var allNewProps = this.getAllProps(savedContent);
+
+ function getNewProp(alias) {
+ return _.find(allNewProps, function (item) {
+ return item.alias === alias;
+ });
+ }
+
+ //a method to ignore built-in prop changes
+ var shouldIgnore = function(propName) {
+ return _.some(["tabs", "notifications", "ModelState", "tabs", "properties"], function(i) {
+ return i === propName;
+ });
+ };
+ //check for changed built-in properties of the content
+ for (var o in origContent) {
+
+ //ignore the ones listed in the array
+ if (shouldIgnore(o)) {
+ continue;
+ }
+
+ if (!_.isEqual(origContent[o], savedContent[o])) {
+ origContent[o] = savedContent[o];
+ }
+ }
+
+ //check for changed properties of the content
+ for (var p in allOrigProps) {
+ var newProp = getNewProp(allOrigProps[p].alias);
+ if (newProp && !_.isEqual(allOrigProps[p].value, newProp.value)) {
+
+ //they have changed so set the origContent prop to the new one
+ var origVal = allOrigProps[p].value;
+ allOrigProps[p].value = newProp.value;
+
+ //instead of having a property editor $watch their expression to check if it has
+ // been updated, instead we'll check for the existence of a special method on their model
+ // and just call it.
+ if (angular.isFunction(allOrigProps[p].onValueChanged)) {
+ //send the newVal + oldVal
+ allOrigProps[p].onValueChanged(allOrigProps[p].value, origVal);
+ }
+
+ changed.push(allOrigProps[p]);
+ }
+ }
+
+ return changed;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.contentEditingHelper#handleSaveError
+ * @methodOf umbraco.services.contentEditingHelper
+ * @function
+ *
+ * @description
+ * A function to handle what happens when we have validation issues from the server side
+ */
+ handleSaveError: function (args) {
+
+ if (!args.err) {
+ throw "args.err cannot be null";
+ }
+ if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) {
+ throw "args.redirectOnFailure must be set to true or false";
+ }
+
+ //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors.
+ //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something).
+ //Or, some strange server error
+ if (args.err.status === 400) {
+ //now we need to look through all the validation errors
+ if (args.err.data && (args.err.data.ModelState)) {
+
+ //wire up the server validation errs
+ formHelper.handleServerValidation(args.err.data.ModelState);
+
+ if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) {
+ //we are not redirecting because this is not new content, it is existing content. In this case
+ // we need to detect what properties have changed and re-bind them with the server data. Then we need
+ // to re-bind any server validation errors after the digest takes place.
+
+ if (args.rebindCallback && angular.isFunction(args.rebindCallback)) {
+ args.rebindCallback();
+ }
+
+ serverValidationManager.executeAndClearAllSubscriptions();
+ }
+
+ //indicates we've handled the server result
+ return true;
+ }
+ else {
+ dialogService.ysodDialog(args.err);
+ }
+ }
+ else {
+ dialogService.ysodDialog(args.err);
+ }
+
+ return false;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.contentEditingHelper#handleSuccessfulSave
+ * @methodOf umbraco.services.contentEditingHelper
+ * @function
+ *
+ * @description
+ * A function to handle when saving a content item is successful. This will rebind the values of the model that have changed
+ * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect
+ * when we're creating new content.
+ */
+ handleSuccessfulSave: function (args) {
+
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.savedContent) {
+ throw "args.savedContent cannot be null";
+ }
+
+ if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) {
+
+ //we are not redirecting because this is not new content, it is existing content. In this case
+ // we need to detect what properties have changed and re-bind them with the server data.
+ //call the callback
+ if (args.rebindCallback && angular.isFunction(args.rebindCallback)) {
+ args.rebindCallback();
+ }
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.contentEditingHelper#redirectToCreatedContent
+ * @methodOf umbraco.services.contentEditingHelper
+ * @function
+ *
+ * @description
+ * Changes the location to be editing the newly created content after create was successful.
+ * We need to decide if we need to redirect to edito mode or if we will remain in create mode.
+ * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID
+ */
+ redirectToCreatedContent: function (id, modelState) {
+
+ //only continue if we are currently in create mode and if there is no 'Name' modelstate errors
+ // since we need at least a name to create content.
+ if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState["Name"]))) {
+
+ //need to change the location to not be in 'create' mode. Currently the route will be something like:
+ // /belle/#/content/edit/1234?doctype=newsArticle&create=true
+ // but we need to remove everything after the query so that it is just:
+ // /belle/#/content/edit/9876 (where 9876 is the new id)
+
+ //clear the query strings
+ $location.search("");
+
+ //change to new path
+ $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id);
+ //don't add a browser history for this
+ $location.replace();
+ return true;
+ }
+ return false;
+ }
+ };
+}
+angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper);
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.contentTypeHelper
+ * @description A helper service for the content type editor
+ **/
+function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $injector, $q) {
+
+ var contentTypeHelperService = {
+
+ createIdArray: function(array) {
+
+ var newArray = [];
+
+ angular.forEach(array, function(arrayItem){
+
+ if(angular.isObject(arrayItem)) {
+ newArray.push(arrayItem.id);
+ } else {
+ newArray.push(arrayItem);
+ }
+
+ });
+
+ return newArray;
+
+ },
+
+ generateModels: function () {
+ var deferred = $q.defer();
+ var modelsResource = $injector.has("modelsBuilderResource") ? $injector.get("modelsBuilderResource") : null;
+ var modelsBuilderEnabled = Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled;
+ if (modelsBuilderEnabled && modelsResource) {
+ modelsResource.buildModels().then(function(result) {
+ deferred.resolve(result);
+
+ //just calling this to get the servar back to life
+ modelsResource.getModelsOutOfDateStatus();
+
+ }, function(e) {
+ deferred.reject(e);
+ });
+ }
+ else {
+ deferred.resolve(false);
+ }
+ return deferred.promise;
+ },
+
+ checkModelsBuilderStatus: function () {
+ var deferred = $q.defer();
+ var modelsResource = $injector.has("modelsBuilderResource") ? $injector.get("modelsBuilderResource") : null;
+ var modelsBuilderEnabled = (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables && Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled === true);
+
+ if (modelsBuilderEnabled && modelsResource) {
+ modelsResource.getModelsOutOfDateStatus().then(function(result) {
+ //Generate models buttons should be enabled if it is 0
+ deferred.resolve(result.status === 0);
+ });
+ }
+ else {
+ deferred.resolve(false);
+ }
+ return deferred.promise;
+ },
+
+ makeObjectArrayFromId: function (idArray, objectArray) {
+ var newArray = [];
+
+ for (var idIndex = 0; idArray.length > idIndex; idIndex++) {
+ var id = idArray[idIndex];
+
+ for (var objectIndex = 0; objectArray.length > objectIndex; objectIndex++) {
+ var object = objectArray[objectIndex];
+ if (id === object.id) {
+ newArray.push(object);
+ }
+ }
+
+ }
+
+ return newArray;
+ },
+
+ validateAddingComposition: function(contentType, compositeContentType) {
+
+ //Validate that by adding this group that we are not adding duplicate property type aliases
+
+ var propertiesAdding = _.flatten(_.map(compositeContentType.groups, function(g) {
+ return _.map(g.properties, function(p) {
+ return p.alias;
+ });
+ }));
+ var propAliasesExisting = _.filter(_.flatten(_.map(contentType.groups, function(g) {
+ return _.map(g.properties, function(p) {
+ return p.alias;
+ });
+ })), function(f) {
+ return f !== null && f !== undefined;
+ });
+
+ var intersec = _.intersection(propertiesAdding, propAliasesExisting);
+ if (intersec.length > 0) {
+ //return the overlapping property aliases
+ return intersec;
+ }
+
+ //no overlapping property aliases
+ return [];
+ },
+
+ mergeCompositeContentType: function(contentType, compositeContentType) {
+
+ //Validate that there are no overlapping aliases
+ var overlappingAliases = this.validateAddingComposition(contentType, compositeContentType);
+ if (overlappingAliases.length > 0) {
+ throw new Error("Cannot add this composition, these properties already exist on the content type: " + overlappingAliases.join());
+ }
+
+ angular.forEach(compositeContentType.groups, function(compositionGroup) {
+
+ // order composition groups based on sort order
+ compositionGroup.properties = $filter('orderBy')(compositionGroup.properties, 'sortOrder');
+
+ // get data type details
+ angular.forEach(compositionGroup.properties, function(property) {
+ dataTypeResource.getById(property.dataTypeId)
+ .then(function(dataType) {
+ property.dataTypeIcon = dataType.icon;
+ property.dataTypeName = dataType.name;
+ });
+ });
+
+ // set inherited state on tab
+ compositionGroup.inherited = true;
+
+ // set inherited state on properties
+ angular.forEach(compositionGroup.properties, function(compositionProperty) {
+ compositionProperty.inherited = true;
+ });
+
+ // set tab state
+ compositionGroup.tabState = "inActive";
+
+ // if groups are named the same - merge the groups
+ angular.forEach(contentType.groups, function(contentTypeGroup) {
+
+ if (contentTypeGroup.name === compositionGroup.name) {
+
+ // set flag to show if properties has been merged into a tab
+ compositionGroup.groupIsMerged = true;
+
+ // make group inherited
+ contentTypeGroup.inherited = true;
+
+ // add properties to the top of the array
+ contentTypeGroup.properties = compositionGroup.properties.concat(contentTypeGroup.properties);
+
+ // update sort order on all properties in merged group
+ contentTypeGroup.properties = contentTypeHelperService.updatePropertiesSortOrder(contentTypeGroup.properties);
+
+ // make parentTabContentTypeNames to an array so we can push values
+ if (contentTypeGroup.parentTabContentTypeNames === null || contentTypeGroup.parentTabContentTypeNames === undefined) {
+ contentTypeGroup.parentTabContentTypeNames = [];
+ }
+
+ // push name to array of merged composite content types
+ contentTypeGroup.parentTabContentTypeNames.push(compositeContentType.name);
+
+ // make parentTabContentTypes to an array so we can push values
+ if (contentTypeGroup.parentTabContentTypes === null || contentTypeGroup.parentTabContentTypes === undefined) {
+ contentTypeGroup.parentTabContentTypes = [];
+ }
+
+ // push id to array of merged composite content types
+ contentTypeGroup.parentTabContentTypes.push(compositeContentType.id);
+
+ // get sort order from composition
+ contentTypeGroup.sortOrder = compositionGroup.sortOrder;
+
+ // splice group to the top of the array
+ var contentTypeGroupCopy = angular.copy(contentTypeGroup);
+ var index = contentType.groups.indexOf(contentTypeGroup);
+ contentType.groups.splice(index, 1);
+ contentType.groups.unshift(contentTypeGroupCopy);
+
+ }
+
+ });
+
+ // if group is not merged - push it to the end of the array - before init tab
+ if (compositionGroup.groupIsMerged === false || compositionGroup.groupIsMerged === undefined) {
+
+ // make parentTabContentTypeNames to an array so we can push values
+ if (compositionGroup.parentTabContentTypeNames === null || compositionGroup.parentTabContentTypeNames === undefined) {
+ compositionGroup.parentTabContentTypeNames = [];
+ }
+
+ // push name to array of merged composite content types
+ compositionGroup.parentTabContentTypeNames.push(compositeContentType.name);
+
+ // make parentTabContentTypes to an array so we can push values
+ if (compositionGroup.parentTabContentTypes === null || compositionGroup.parentTabContentTypes === undefined) {
+ compositionGroup.parentTabContentTypes = [];
+ }
+
+ // push id to array of merged composite content types
+ compositionGroup.parentTabContentTypes.push(compositeContentType.id);
+
+ // push group before placeholder tab
+ contentType.groups.unshift(compositionGroup);
+
+ }
+
+ });
+
+ // sort all groups by sortOrder property
+ contentType.groups = $filter('orderBy')(contentType.groups, 'sortOrder');
+
+ return contentType;
+
+ },
+
+ splitCompositeContentType: function (contentType, compositeContentType) {
+
+ var groups = [];
+
+ angular.forEach(contentType.groups, function(contentTypeGroup){
+
+ if( contentTypeGroup.tabState !== "init" ) {
+
+ var idIndex = contentTypeGroup.parentTabContentTypes.indexOf(compositeContentType.id);
+ var nameIndex = contentTypeGroup.parentTabContentTypeNames.indexOf(compositeContentType.name);
+ var groupIndex = contentType.groups.indexOf(contentTypeGroup);
+
+
+ if( idIndex !== -1 ) {
+
+ var properties = [];
+
+ // remove all properties from composite content type
+ angular.forEach(contentTypeGroup.properties, function(property){
+ if(property.contentTypeId !== compositeContentType.id) {
+ properties.push(property);
+ }
+ });
+
+ // set new properties array to properties
+ contentTypeGroup.properties = properties;
+
+ // remove composite content type name and id from inherited arrays
+ contentTypeGroup.parentTabContentTypes.splice(idIndex, 1);
+ contentTypeGroup.parentTabContentTypeNames.splice(nameIndex, 1);
+
+ // remove inherited state if there are no inherited properties
+ if(contentTypeGroup.parentTabContentTypes.length === 0) {
+ contentTypeGroup.inherited = false;
+ }
+
+ // remove group if there are no properties left
+ if(contentTypeGroup.properties.length > 1) {
+ //contentType.groups.splice(groupIndex, 1);
+ groups.push(contentTypeGroup);
+ }
+
+ } else {
+ groups.push(contentTypeGroup);
+ }
+
+ } else {
+ groups.push(contentTypeGroup);
+ }
+
+ // update sort order on properties
+ contentTypeGroup.properties = contentTypeHelperService.updatePropertiesSortOrder(contentTypeGroup.properties);
+
+ });
+
+ contentType.groups = groups;
+
+ },
+
+ updatePropertiesSortOrder: function (properties) {
+
+ var sortOrder = 0;
+
+ angular.forEach(properties, function(property) {
+ if( !property.inherited && property.propertyState !== "init") {
+ property.sortOrder = sortOrder;
+ }
+ sortOrder++;
+ });
+
+ return properties;
+
+ },
+
+ getTemplatePlaceholder: function() {
+
+ var templatePlaceholder = {
+ "name": "",
+ "icon": "icon-layout",
+ "alias": "templatePlaceholder",
+ "placeholder": true
+ };
+
+ return templatePlaceholder;
+
+ },
+
+ insertDefaultTemplatePlaceholder: function(defaultTemplate) {
+
+ // get template placeholder
+ var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder();
+
+ // add as default template
+ defaultTemplate = templatePlaceholder;
+
+ return defaultTemplate;
+
+ },
+
+ insertTemplatePlaceholder: function(array) {
+
+ // get template placeholder
+ var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder();
+
+ // add as selected item
+ array.push(templatePlaceholder);
+
+ return array;
+
+ },
+
+ insertChildNodePlaceholder: function (array, name, icon, id) {
+
+ var placeholder = {
+ "name": name,
+ "icon": icon,
+ "id": id
+ };
+
+ array.push(placeholder);
+
+ }
+
+ };
+
+ return contentTypeHelperService;
+}
+angular.module('umbraco.services').factory('contentTypeHelper', contentTypeHelper);
+
+/**
+* @ngdoc service
+* @name umbraco.services.cropperHelper
+* @description A helper object used for dealing with image cropper data
+**/
+function cropperHelper(umbRequestHelper, $http) {
+ var service = {
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.cropperHelper#configuration
+ * @methodOf umbraco.services.cropperHelper
+ *
+ * @description
+ * Returns a collection of plugins available to the tinyMCE editor
+ *
+ */
+ configuration: function (mediaTypeAlias) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "imageCropperApiBaseUrl",
+ "GetConfiguration",
+ [{ mediaTypeAlias: mediaTypeAlias}])),
+ 'Failed to retrieve tinymce configuration');
+ },
+
+
+ //utill for getting either min/max aspect ratio to scale image after
+ calculateAspectRatioFit : function(srcWidth, srcHeight, maxWidth, maxHeight, maximize) {
+ var ratio = [maxWidth / srcWidth, maxHeight / srcHeight ];
+
+ if(maximize){
+ ratio = Math.max(ratio[0], ratio[1]);
+ }else{
+ ratio = Math.min(ratio[0], ratio[1]);
+ }
+
+ return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio};
+ },
+
+ //utill for scaling width / height given a ratio
+ calculateSizeToRatio : function(srcWidth, srcHeight, ratio) {
+ return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio};
+ },
+
+ scaleToMaxSize : function(srcWidth, srcHeight, maxSize) {
+
+ var retVal = {height: srcHeight, width: srcWidth};
+
+ if(srcWidth > maxSize ||srcHeight > maxSize){
+ var ratio = [maxSize / srcWidth, maxSize / srcHeight ];
+ ratio = Math.min(ratio[0], ratio[1]);
+
+ retVal.height = srcHeight * ratio;
+ retVal.width = srcWidth * ratio;
+ }
+
+ return retVal;
+ },
+
+ //returns a ng-style object with top,left,width,height pixel measurements
+ //expects {left,right,top,bottom} - {width,height}, {width,height}, int
+ //offset is just to push the image position a number of pixels from top,left
+ convertToStyle : function(coordinates, originalSize, viewPort, offset){
+
+ var coordinates_px = service.coordinatesToPixels(coordinates, originalSize, offset);
+ var _offset = offset || 0;
+
+ var x = 1 - (coordinates.x1 + Math.abs(coordinates.x2));
+ var left_of_x = originalSize.width * x;
+ var ratio = viewPort.width / left_of_x;
+
+ var style = {
+ position: "absolute",
+ top: -(coordinates_px.y1*ratio)+ _offset,
+ left: -(coordinates_px.x1* ratio)+ _offset,
+ width: Math.floor(originalSize.width * ratio),
+ height: Math.floor(originalSize.height * ratio),
+ originalWidth: originalSize.width,
+ originalHeight: originalSize.height,
+ ratio: ratio
+ };
+
+ return style;
+ },
+
+
+ coordinatesToPixels : function(coordinates, originalSize, offset){
+
+ var coordinates_px = {
+ x1: Math.floor(coordinates.x1 * originalSize.width),
+ y1: Math.floor(coordinates.y1 * originalSize.height),
+ x2: Math.floor(coordinates.x2 * originalSize.width),
+ y2: Math.floor(coordinates.y2 * originalSize.height)
+ };
+
+ return coordinates_px;
+ },
+
+ pixelsToCoordinates : function(image, width, height, offset){
+
+ var x1_px = Math.abs(image.left-offset);
+ var y1_px = Math.abs(image.top-offset);
+
+ var x2_px = image.width - (x1_px + width);
+ var y2_px = image.height - (y1_px + height);
+
+
+ //crop coordinates in %
+ var crop = {};
+ crop.x1 = x1_px / image.width;
+ crop.y1 = y1_px / image.height;
+ crop.x2 = x2_px / image.width;
+ crop.y2 = y2_px / image.height;
+
+ for(var coord in crop){
+ if(crop[coord] < 0){
+ crop[coord] = 0;
+ }
+ }
+
+ return crop;
+ },
+
+ centerInsideViewPort : function(img, viewport){
+ var left = viewport.width/ 2 - img.width / 2,
+ top = viewport.height / 2 - img.height / 2;
+
+ return {left: left, top: top};
+ },
+
+ alignToCoordinates : function(image, center, viewport){
+
+ var min_left = (image.width) - (viewport.width);
+ var min_top = (image.height) - (viewport.height);
+
+ var c_top = -(center.top * image.height) + (viewport.height / 2);
+ var c_left = -(center.left * image.width) + (viewport.width / 2);
+
+ if(c_top < -min_top){
+ c_top = -min_top;
+ }
+ if(c_top > 0){
+ c_top = 0;
+ }
+ if(c_left < -min_left){
+ c_left = -min_left;
+ }
+ if(c_left > 0){
+ c_left = 0;
+ }
+ return {left: c_left, top: c_top};
+ },
+
+
+ syncElements : function(source, target){
+ target.height(source.height());
+ target.width(source.width());
+
+ target.css({
+ "top": source[0].offsetTop,
+ "left": source[0].offsetLeft
+ });
+ }
+ };
+
+ return service;
+}
+
+angular.module('umbraco.services').factory('cropperHelper', cropperHelper);
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.dataTypeHelper
+ * @description A helper service for data types
+ **/
+function dataTypeHelper() {
+
+ var dataTypeHelperService = {
+
+ createPreValueProps: function(preVals) {
+
+ var preValues = [];
+
+ for (var i = 0; i < preVals.length; i++) {
+ preValues.push({
+ hideLabel: preVals[i].hideLabel,
+ alias: preVals[i].key,
+ description: preVals[i].description,
+ label: preVals[i].label,
+ view: preVals[i].view,
+ value: preVals[i].value
+ });
+ }
+
+ return preValues;
+
+ },
+
+ rebindChangedProperties: function (origContent, savedContent) {
+
+ //a method to ignore built-in prop changes
+ var shouldIgnore = function (propName) {
+ return _.some(["notifications", "ModelState"], function (i) {
+ return i === propName;
+ });
+ };
+ //check for changed built-in properties of the content
+ for (var o in origContent) {
+
+ //ignore the ones listed in the array
+ if (shouldIgnore(o)) {
+ continue;
+ }
+
+ if (!_.isEqual(origContent[o], savedContent[o])) {
+ origContent[o] = savedContent[o];
+ }
+ }
+ }
+
+ };
+
+ return dataTypeHelperService;
+}
+angular.module('umbraco.services').factory('dataTypeHelper', dataTypeHelper);
+/**
+ * @ngdoc service
+ * @name umbraco.services.dialogService
+ *
+ * @requires $rootScope
+ * @requires $compile
+ * @requires $http
+ * @requires $log
+ * @requires $q
+ * @requires $templateCache
+ *
+ * @description
+ * Application-wide service for handling modals, overlays and dialogs
+ * By default it injects the passed template url into a div to body of the document
+ * And renders it, but does also support rendering items in an iframe, incase
+ * serverside processing is needed, or its a non-angular page
+ *
+ * ##usage
+ * To use, simply inject the dialogService into any controller that needs it, and make
+ * sure the umbraco.services module is accesible - which it should be by default.
+ *
+ *
+ * var dialog = dialogService.open({template: 'path/to/page.html', show: true, callback: done});
+ * functon done(data){
+ * //The dialog has been submitted
+ * //data contains whatever the dialog has selected / attached
+ * }
+ *
+ */
+
+angular.module('umbraco.services')
+.factory('dialogService', function ($rootScope, $compile, $http, $timeout, $q, $templateCache, appState, eventsService) {
+
+ var dialogs = [];
+
+ /** Internal method that removes all dialogs */
+ function removeAllDialogs(args) {
+ for (var i = 0; i < dialogs.length; i++) {
+ var dialog = dialogs[i];
+
+ //very special flag which means that global events cannot close this dialog - currently only used on the login
+ // dialog since it's special and cannot be closed without logging in.
+ if (!dialog.manualClose) {
+ dialog.close(args);
+ }
+
+ }
+ }
+
+ /** Internal method that closes the dialog properly and cleans up resources */
+ function closeDialog(dialog) {
+
+ if (dialog.element) {
+ dialog.element.modal('hide');
+
+ //this is not entirely enough since the damn webforms scriploader still complains
+ if (dialog.iframe) {
+ dialog.element.find("iframe").attr("src", "about:blank");
+ }
+
+ dialog.scope.$destroy();
+
+ //we need to do more than just remove the element, this will not destroy the
+ // scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont
+ // take care of this ourselves we have memory leaks.
+ dialog.element.remove();
+
+ //remove 'this' dialog from the dialogs array
+ dialogs = _.reject(dialogs, function (i) { return i === dialog; });
+ }
+ }
+
+ /** Internal method that handles opening all dialogs */
+ function openDialog(options) {
+ var defaults = {
+ container: $("body"),
+ animation: "fade",
+ modalClass: "umb-modal",
+ width: "100%",
+ inline: false,
+ iframe: false,
+ show: true,
+ template: "views/common/notfound.html",
+ callback: undefined,
+ closeCallback: undefined,
+ element: undefined,
+ // It will set this value as a property on the dialog controller's scope as dialogData,
+ // used to pass in custom data to the dialog controller's $scope. Though this is near identical to
+ // the dialogOptions property that is also set the the dialog controller's $scope object.
+ // So there's basically 2 ways of doing the same thing which we're now stuck with and in fact
+ // dialogData has another specially attached property called .selection which gets used.
+ dialogData: undefined
+ };
+
+ var dialog = angular.extend(defaults, options);
+
+ //NOTE: People should NOT pass in a scope object that is legacy functoinality and causes problems. We will ALWAYS
+ // destroy the scope when the dialog is closed regardless if it is in use elsewhere which is why it shouldn't be done.
+ var scope = options.scope || $rootScope.$new();
+
+ //Modal dom obj and set id to old-dialog-service - used until we get all dialogs moved the the new overlay directive
+ dialog.element = $('');
+ var id = "old-dialog-service";
+
+ if (options.inline) {
+ dialog.animation = "";
+ }
+ else {
+ dialog.element.addClass("modal");
+ dialog.element.addClass("hide");
+ }
+
+ //set the id and add classes
+ dialog.element
+ .attr('id', id)
+ .addClass(dialog.animation)
+ .addClass(dialog.modalClass);
+
+ //push the modal into the global modal collection
+ //we halt the .push because a link click will trigger a closeAll right away
+ $timeout(function () {
+ dialogs.push(dialog);
+ }, 500);
+
+
+ dialog.close = function (data) {
+ if (dialog.closeCallback) {
+ dialog.closeCallback(data);
+ }
+
+ closeDialog(dialog);
+ };
+
+ //if iframe is enabled, inject that instead of a template
+ if (dialog.iframe) {
+ var html = $("");
+ dialog.element.html(html);
+
+ //append to body or whatever element is passed in as options.containerElement
+ dialog.container.append(dialog.element);
+
+ // Compile modal content
+ $timeout(function () {
+ $compile(dialog.element)(dialog.scope);
+ });
+
+ dialog.element.css("width", dialog.width);
+
+ //Autoshow
+ if (dialog.show) {
+ dialog.element.modal('show');
+ }
+
+ dialog.scope = scope;
+ return dialog;
+ }
+ else {
+
+ //We need to load the template with an httpget and once it's loaded we'll compile and assign the result to the container
+ // object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the
+ // $modal object so we'll actually return the modal object synchronously without waiting for the promise. Otherwise this openDialog
+ // method will always need to return a promise which gets nasty because of promises in promises plus the result just needs a reference
+ // to the $modal object which will not change (only it's contents will change).
+ $q.when($templateCache.get(dialog.template) || $http.get(dialog.template, { cache: true }).then(function (res) { return res.data; }))
+ .then(function onSuccess(template) {
+
+ // Build modal object
+ dialog.element.html(template);
+
+ //append to body or other container element
+ dialog.container.append(dialog.element);
+
+ // Compile modal content
+ $timeout(function () {
+ $compile(dialog.element)(scope);
+ });
+
+ scope.dialogOptions = dialog;
+
+ //Scope to handle data from the modal form
+ scope.dialogData = dialog.dialogData ? dialog.dialogData : {};
+ scope.dialogData.selection = [];
+
+ // Provide scope display functions
+ //this passes the modal to the current scope
+ scope.$modal = function (name) {
+ dialog.element.modal(name);
+ };
+
+ scope.swipeHide = function (e) {
+
+ if (appState.getGlobalState("touchDevice")) {
+ var selection = window.getSelection();
+ if (selection.type !== "Range") {
+ scope.hide();
+ }
+ }
+ };
+
+ //NOTE: Same as 'close' without the callbacks
+ scope.hide = function () {
+ closeDialog(dialog);
+ };
+
+ //basic events for submitting and closing
+ scope.submit = function (data) {
+ if (dialog.callback) {
+ dialog.callback(data);
+ }
+
+ closeDialog(dialog);
+ };
+
+ scope.close = function (data) {
+ dialog.close(data);
+ };
+
+ //NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow).
+ // You CANNOT call show() after you call hide(). hide = close, they are the same thing and once
+ // a dialog is closed it's resources are disposed of.
+ scope.show = function () {
+ if (dialog.manualClose === true) {
+ //show and configure that the keyboard events are not enabled on this modal
+ dialog.element.modal({ keyboard: false });
+ }
+ else {
+ //just show normally
+ dialog.element.modal('show');
+ }
+
+ };
+
+ scope.select = function (item) {
+ var i = scope.dialogData.selection.indexOf(item);
+ if (i < 0) {
+ scope.dialogData.selection.push(item);
+ } else {
+ scope.dialogData.selection.splice(i, 1);
+ }
+ };
+
+ //NOTE: Same as 'close' without the callbacks
+ scope.dismiss = scope.hide;
+
+ // Emit modal events
+ angular.forEach(['show', 'shown', 'hide', 'hidden'], function (name) {
+ dialog.element.on(name, function (ev) {
+ scope.$emit('modal-' + name, ev);
+ });
+ });
+
+ // Support autofocus attribute
+ dialog.element.on('shown', function (event) {
+ $('input[autofocus]', dialog.element).first().trigger('focus');
+ });
+
+ dialog.scope = scope;
+
+ //Autoshow
+ if (dialog.show) {
+ scope.show();
+ }
+
+ });
+
+ //Return the modal object outside of the promise!
+ return dialog;
+ }
+ }
+
+ /** Handles the closeDialogs event */
+ eventsService.on("app.closeDialogs", function (evt, args) {
+ removeAllDialogs(args);
+ });
+
+ return {
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#open
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a modal rendering a given template url.
+ *
+ * @param {Object} options rendering options
+ * @param {DomElement} options.container the DOM element to inject the modal into, by default set to body
+ * @param {Function} options.callback function called when the modal is submitted
+ * @param {String} options.template the url of the template
+ * @param {String} options.animation animation csss class, by default set to "fade"
+ * @param {String} options.modalClass modal css class, by default "umb-modal"
+ * @param {Bool} options.show show the modal instantly
+ * @param {Bool} options.iframe load template in an iframe, only needed for serverside templates
+ * @param {Int} options.width set a width on the modal, only needed for iframes
+ * @param {Bool} options.inline strips the modal from any animation and wrappers, used when you want to inject a dialog into an existing container
+ * @returns {Object} modal object
+ */
+ open: function (options) {
+ return openDialog(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#close
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Closes a specific dialog
+ * @param {Object} dialog the dialog object to close
+ * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs.
+ */
+ close: function (dialog, args) {
+ if (dialog) {
+ dialog.close(args);
+ }
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#closeAll
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Closes all dialogs
+ * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs.
+ */
+ closeAll: function (args) {
+ removeAllDialogs(args);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#mediaPicker
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a media picker in a modal, the callback returns an array of selected media items
+ * @param {Object} options mediapicker dialog options object
+ * @param {Boolean} options.onlyImages Only display files that have an image file-extension
+ * @param {Function} options.callback callback function
+ * @returns {Object} modal object
+ */
+ mediaPicker: function (options) {
+ options.template = 'views/common/dialogs/mediaPicker.html';
+ options.show = true;
+ return openDialog(options);
+ },
+
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#contentPicker
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a content picker tree in a modal, the callback returns an array of selected documents
+ * @param {Object} options content picker dialog options object
+ * @param {Boolean} options.multiPicker should the picker return one or multiple items
+ * @param {Function} options.callback callback function
+ * @returns {Object} modal object
+ */
+ contentPicker: function (options) {
+
+ options.treeAlias = "content";
+ options.section = "content";
+
+ return this.treePicker(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#linkPicker
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a link picker tree in a modal, the callback returns a single link
+ * @param {Object} options content picker dialog options object
+ * @param {Function} options.callback callback function
+ * @returns {Object} modal object
+ */
+ linkPicker: function (options) {
+ options.template = 'views/common/dialogs/linkPicker.html';
+ options.show = true;
+ return openDialog(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#macroPicker
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a mcaro picker in a modal, the callback returns a object representing the macro and it's parameters
+ * @param {Object} options macropicker dialog options object
+ * @param {Function} options.callback callback function
+ * @returns {Object} modal object
+ */
+ macroPicker: function (options) {
+ options.template = 'views/common/dialogs/insertmacro.html';
+ options.show = true;
+ options.modalClass = "span7 umb-modal";
+ return openDialog(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#memberPicker
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a member picker in a modal, the callback returns a object representing the selected member
+ * @param {Object} options member picker dialog options object
+ * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning
+ * @param {Function} options.callback callback function
+ * @returns {Object} modal object
+ */
+ memberPicker: function (options) {
+
+ options.treeAlias = "member";
+ options.section = "member";
+
+ return this.treePicker(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#memberGroupPicker
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a member group picker in a modal, the callback returns a object representing the selected member
+ * @param {Object} options member group picker dialog options object
+ * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning
+ * @param {Function} options.callback callback function
+ * @returns {Object} modal object
+ */
+ memberGroupPicker: function (options) {
+ options.template = 'views/common/dialogs/memberGroupPicker.html';
+ options.show = true;
+ return openDialog(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#iconPicker
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a icon picker in a modal, the callback returns a object representing the selected icon
+ * @param {Object} options iconpicker dialog options object
+ * @param {Function} options.callback callback function
+ * @returns {Object} modal object
+ */
+ iconPicker: function (options) {
+ options.template = 'views/common/dialogs/iconPicker.html';
+ options.show = true;
+ return openDialog(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#treePicker
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a tree picker in a modal, the callback returns a object representing the selected tree item
+ * @param {Object} options iconpicker dialog options object
+ * @param {String} options.section tree section to display
+ * @param {String} options.treeAlias specific tree to display
+ * @param {Boolean} options.multiPicker should the tree pick one or multiple items before returning
+ * @param {Function} options.callback callback function
+ * @returns {Object} modal object
+ */
+ treePicker: function (options) {
+ options.template = 'views/common/dialogs/treePicker.html';
+ options.show = true;
+ return openDialog(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#propertyDialog
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a dialog with a chosen property editor in, a value can be passed to the modal, and this value is returned in the callback
+ * @param {Object} options mediapicker dialog options object
+ * @param {Function} options.callback callback function
+ * @param {String} editor editor to use to edit a given value and return on callback
+ * @param {Object} value value sent to the property editor
+ * @returns {Object} modal object
+ */
+ //TODO: Wtf does this do? I don't think anything!
+ propertyDialog: function (options) {
+ options.template = 'views/common/dialogs/property.html';
+ options.show = true;
+ return openDialog(options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#embedDialog
+ * @methodOf umbraco.services.dialogService
+ * @description
+ * Opens a dialog to an embed dialog
+ */
+ embedDialog: function (options) {
+ options.template = 'views/common/dialogs/rteembed.html';
+ options.show = true;
+ return openDialog(options);
+ },
+ /**
+ * @ngdoc method
+ * @name umbraco.services.dialogService#ysodDialog
+ * @methodOf umbraco.services.dialogService
+ *
+ * @description
+ * Opens a dialog to show a custom YSOD
+ */
+ ysodDialog: function (ysodError) {
+
+ var newScope = $rootScope.$new();
+ newScope.error = ysodError;
+ return openDialog({
+ modalClass: "umb-modal wide ysod",
+ scope: newScope,
+ //callback: options.callback,
+ template: 'views/common/dialogs/ysod.html',
+ show: true
+ });
+ },
+
+ confirmDialog: function (ysodError) {
+
+ options.template = 'views/common/dialogs/confirm.html';
+ options.show = true;
+ return openDialog(options);
+ }
+ };
+});
+
+/** Used to broadcast and listen for global events and allow the ability to add async listeners to the callbacks */
+
+/*
+ Core app events:
+
+ app.ready
+ app.authenticated
+ app.notAuthenticated
+ app.closeDialogs
+*/
+
+function eventsService($q, $rootScope) {
+
+ return {
+
+ /** raise an event with a given name, returns an array of promises for each listener */
+ emit: function (name, args) {
+
+ //there are no listeners
+ if (!$rootScope.$$listeners[name]) {
+ return;
+ //return [];
+ }
+
+ //send the event
+ $rootScope.$emit(name, args);
+
+
+ //PP: I've commented out the below, since we currently dont
+ // expose the eventsService as a documented api
+ // and think we need to figure out our usecases for this
+ // since the below modifies the return value of the then on() method
+ /*
+ //setup a deferred promise for each listener
+ var deferred = [];
+ for (var i = 0; i < $rootScope.$$listeners[name].length; i++) {
+ deferred.push($q.defer());
+ }*/
+
+ //create a new event args object to pass to the
+ // $emit containing methods that will allow listeners
+ // to return data in an async if required
+ /*
+ var eventArgs = {
+ args: args,
+ reject: function (a) {
+ deferred.pop().reject(a);
+ },
+ resolve: function (a) {
+ deferred.pop().resolve(a);
+ }
+ };*/
+
+
+
+ /*
+ //return an array of promises
+ var promises = _.map(deferred, function(p) {
+ return p.promise;
+ });
+ return promises;*/
+ },
+
+ /** subscribe to a method, or use scope.$on = same thing */
+ on: function(name, callback) {
+ return $rootScope.$on(name, callback);
+ },
+
+ /** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */
+ unsubscribe: function(handle) {
+ if (angular.isFunction(handle)) {
+ handle();
+ }
+ }
+ };
+}
+
+angular.module('umbraco.services').factory('eventsService', eventsService);
+/**
+ * @ngdoc service
+ * @name umbraco.services.fileManager
+ * @function
+ *
+ * @description
+ * Used by editors to manage any files that require uploading with the posted data, normally called by property editors
+ * that need to attach files.
+ * When a route changes successfully, we ensure that the collection is cleared.
+ */
+function fileManager() {
+
+ var fileCollection = [];
+
+ return {
+ /**
+ * @ngdoc function
+ * @name umbraco.services.fileManager#addFiles
+ * @methodOf umbraco.services.fileManager
+ * @function
+ *
+ * @description
+ * Attaches files to the current manager for the current editor for a particular property, if an empty array is set
+ * for the files collection that effectively clears the files for the specified editor.
+ */
+ setFiles: function(propertyAlias, files) {
+ //this will clear the files for the current property and then add the new ones for the current property
+ fileCollection = _.reject(fileCollection, function (item) {
+ return item.alias === propertyAlias;
+ });
+ for (var i = 0; i < files.length; i++) {
+ //save the file object to the files collection
+ fileCollection.push({ alias: propertyAlias, file: files[i] });
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.fileManager#getFiles
+ * @methodOf umbraco.services.fileManager
+ * @function
+ *
+ * @description
+ * Returns all of the files attached to the file manager
+ */
+ getFiles: function() {
+ return fileCollection;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.fileManager#clearFiles
+ * @methodOf umbraco.services.fileManager
+ * @function
+ *
+ * @description
+ * Removes all files from the manager
+ */
+ clearFiles: function () {
+ fileCollection = [];
+ }
+};
+}
+
+angular.module('umbraco.services').factory('fileManager', fileManager);
+/**
+ * @ngdoc service
+ * @name umbraco.services.formHelper
+ * @function
+ *
+ * @description
+ * A utility class used to streamline how forms are developed, to ensure that validation is check and displayed consistently and to ensure that the correct events
+ * fire when they need to.
+ */
+function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService, dialogService, localizationService) {
+ return {
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.formHelper#submitForm
+ * @methodOf umbraco.services.formHelper
+ * @function
+ *
+ * @description
+ * Called by controllers when submitting a form - this ensures that all client validation is checked,
+ * server validation is cleared, that the correct events execute and status messages are displayed.
+ * This returns true if the form is valid, otherwise false if form submission cannot continue.
+ *
+ * @param {object} args An object containing arguments for form submission
+ */
+ submitForm: function (args) {
+
+ var currentForm;
+
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.scope) {
+ throw "args.scope cannot be null";
+ }
+ if (!args.formCtrl) {
+ //try to get the closest form controller
+ currentForm = angularHelper.getRequiredCurrentForm(args.scope);
+ }
+ else {
+ currentForm = args.formCtrl;
+ }
+ //if no statusPropertyName is set we'll default to formStatus.
+ if (!args.statusPropertyName) {
+ args.statusPropertyName = "formStatus";
+ }
+ //if no statusTimeout is set, we'll default to 2500 ms
+ if (!args.statusTimeout) {
+ args.statusTimeout = 2500;
+ }
+
+ //the first thing any form must do is broadcast the formSubmitting event
+ args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action });
+
+ //then check if the form is valid
+ if (!args.skipValidation) {
+ if (currentForm.$invalid) {
+ return false;
+ }
+ }
+
+ //reset the server validations
+ serverValidationManager.reset();
+
+ //check if a form status should be set on the scope
+ if (args.statusMessage) {
+ args.scope[args.statusPropertyName] = args.statusMessage;
+
+ //clear the message after the timeout
+ $timeout(function () {
+ args.scope[args.statusPropertyName] = undefined;
+ }, args.statusTimeout);
+ }
+
+ return true;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.formHelper#submitForm
+ * @methodOf umbraco.services.formHelper
+ * @function
+ *
+ * @description
+ * Called by controllers when a form has been successfully submitted. the correct events execute
+ * and that the notifications are displayed if there are any.
+ *
+ * @param {object} args An object containing arguments for form submission
+ */
+ resetForm: function (args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.scope) {
+ throw "args.scope cannot be null";
+ }
+
+ //if no statusPropertyName is set we'll default to formStatus.
+ if (!args.statusPropertyName) {
+ args.statusPropertyName = "formStatus";
+ }
+ //clear the status
+ args.scope[args.statusPropertyName] = null;
+
+ if (angular.isArray(args.notifications)) {
+ for (var i = 0; i < args.notifications.length; i++) {
+ notificationsService.showNotification(args.notifications[i]);
+ }
+ }
+
+ args.scope.$broadcast("formSubmitted", { scope: args.scope });
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.formHelper#handleError
+ * @methodOf umbraco.services.formHelper
+ * @function
+ *
+ * @description
+ * Needs to be called when a form submission fails, this will wire up all server validation errors in ModelState and
+ * add the correct messages to the notifications. If a server error has occurred this will show a ysod.
+ *
+ * @param {object} err The error object returned from the http promise
+ */
+ handleError: function (err) {
+ //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors.
+ //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something).
+ //Or, some strange server error
+ if (err.status === 400) {
+ //now we need to look through all the validation errors
+ if (err.data && (err.data.ModelState)) {
+
+ //wire up the server validation errs
+ this.handleServerValidation(err.data.ModelState);
+
+ //execute all server validation events and subscribers
+ serverValidationManager.executeAndClearAllSubscriptions();
+ }
+ else {
+ dialogService.ysodDialog(err);
+ }
+ }
+ else {
+ dialogService.ysodDialog(err);
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.formHelper#handleServerValidation
+ * @methodOf umbraco.services.formHelper
+ * @function
+ *
+ * @description
+ * This wires up all of the server validation model state so that valServer and valServerField directives work
+ *
+ * @param {object} err The error object returned from the http promise
+ */
+ handleServerValidation: function (modelState) {
+ for (var e in modelState) {
+
+ //This is where things get interesting....
+ // We need to support validation for all editor types such as both the content and content type editors.
+ // The Content editor ModelState is quite specific with the way that Properties are validated especially considering
+ // that each property is a User Developer property editor.
+ // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations
+ // system.
+ // So, to do this (since we need to support backwards compat), we need to hack a little bit. For Content Properties,
+ // which are user defined, we know that they will exist with a prefixed ModelState of "_Properties.", so if we detect
+ // this, then we know it's a Property.
+
+ //the alias in model state can be in dot notation which indicates
+ // * the first part is the content property alias
+ // * the second part is the field to which the valiation msg is associated with
+ //There will always be at least 2 parts for properties since all model errors for properties are prefixed with "Properties"
+ //If it is not prefixed with "Properties" that means the error is for a field of the object directly.
+
+ var parts = e.split(".");
+
+ //Check if this is for content properties - specific to content/media/member editors because those are special
+ // user defined properties with custom controls.
+ if (parts.length > 1 && parts[0] === "_Properties") {
+
+ var propertyAlias = parts[1];
+
+ //if it contains 2 '.' then we will wire it up to a property's field
+ if (parts.length > 2) {
+ //add an error with a reference to the field for which the validation belongs too
+ serverValidationManager.addPropertyError(propertyAlias, parts[2], modelState[e][0]);
+ }
+ else {
+ //add a generic error for the property, no reference to a specific field
+ serverValidationManager.addPropertyError(propertyAlias, "", modelState[e][0]);
+ }
+
+ }
+ else {
+
+ //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example:
+ // Groups[0].Properties[2].Alias
+ serverValidationManager.addFieldError(e, modelState[e][0]);
+ }
+
+ //add to notifications
+ notificationsService.error("Validation", modelState[e][0]);
+
+ }
+ }
+ };
+}
+angular.module('umbraco.services').factory('formHelper', formHelper);
+angular.module('umbraco.services')
+ .factory('gridService', function ($http, $q){
+
+ var configPath = Umbraco.Sys.ServerVariables.umbracoUrls.gridConfig;
+ var service = {
+ getGridEditors: function () {
+ return $http.get(configPath);
+ }
+ };
+
+ return service;
+
+ });
+
+angular.module('umbraco.services')
+ .factory('helpService', function ($http, $q){
+ var helpTopics = {};
+
+ var defaultUrl = "http://our.umbraco.org/rss/help";
+ var tvUrl = "http://umbraco.tv/feeds/help";
+
+ function getCachedHelp(url){
+ if(helpTopics[url]){
+ return helpTopics[cacheKey];
+ }else{
+ return null;
+ }
+ }
+
+ function setCachedHelp(url, data){
+ helpTopics[url] = data;
+ }
+
+ function fetchUrl(url){
+ var deferred = $q.defer();
+ var found = getCachedHelp(url);
+
+ if(found){
+ deferred.resolve(found);
+ }else{
+
+ var proxyUrl = "dashboard/feedproxy.aspx?url=" + url;
+ $http.get(proxyUrl).then(function(data){
+ var feed = $(data.data);
+ var topics = [];
+
+ $('item', feed).each(function (i, item) {
+ var topic = {};
+ topic.thumbnail = $(item).find('thumbnail').attr('url');
+ topic.title = $("title", item).text();
+ topic.link = $("guid", item).text();
+ topic.description = $("description", item).text();
+ topics.push(topic);
+ });
+
+ setCachedHelp(topics);
+ deferred.resolve(topics);
+ });
+ }
+
+ return deferred.promise;
+ }
+
+
+
+ var service = {
+ findHelp: function (args) {
+ var url = service.getUrl(defaultUrl, args);
+ return fetchUrl(url);
+ },
+
+ findVideos: function (args) {
+ var url = service.getUrl(tvUrl, args);
+ return fetchUrl(url);
+ },
+
+ getUrl: function(url, args){
+ return url + "?" + $.param(args);
+ }
+ };
+
+ return service;
+
+ });
+/**
+ * @ngdoc service
+ * @name umbraco.services.historyService
+ *
+ * @requires $rootScope
+ * @requires $timeout
+ * @requires angularHelper
+ *
+ * @description
+ * Service to handle the main application navigation history. Responsible for keeping track
+ * of where a user navigates to, stores an icon, url and name in a collection, to make it easy
+ * for the user to go back to a previous editor / action
+ *
+ * **Note:** only works with new angular-based editors, not legacy ones
+ *
+ * ##usage
+ * To use, simply inject the historyService into any controller that needs it, and make
+ * sure the umbraco.services module is accesible - which it should be by default.
+ *
+ *
+ */
+angular.module('umbraco.services')
+.factory('historyService', function ($rootScope, $timeout, angularHelper, eventsService) {
+
+ var nArray = [];
+
+ function add(item) {
+
+ if (!item) {
+ return null;
+ }
+
+ var listWithoutThisItem = _.reject(nArray, function(i) {
+ return i.link === item.link;
+ });
+
+ //put it at the top and reassign
+ listWithoutThisItem.splice(0, 0, item);
+ nArray = listWithoutThisItem;
+ return nArray[0];
+
+ }
+
+ return {
+ /**
+ * @ngdoc method
+ * @name umbraco.services.historyService#add
+ * @methodOf umbraco.services.historyService
+ *
+ * @description
+ * Adds a given history item to the users history collection.
+ *
+ * @param {Object} item the history item
+ * @param {String} item.icon icon css class for the list, ex: "icon-image", "icon-doc"
+ * @param {String} item.link route to the editor, ex: "/content/edit/1234"
+ * @param {String} item.name friendly name for the history listing
+ * @returns {Object} history item object
+ */
+ add: function (item) {
+ var icon = item.icon || "icon-file";
+ angularHelper.safeApply($rootScope, function () {
+ var result = add({ name: item.name, icon: icon, link: item.link, time: new Date() });
+ eventsService.emit("historyService.add", {added: result, all: nArray});
+ return result;
+ });
+ },
+ /**
+ * @ngdoc method
+ * @name umbraco.services.historyService#remove
+ * @methodOf umbraco.services.historyService
+ *
+ * @description
+ * Removes a history item from the users history collection, given an index to remove from.
+ *
+ * @param {Int} index index to remove item from
+ */
+ remove: function (index) {
+ angularHelper.safeApply($rootScope, function() {
+ var result = nArray.splice(index, 1);
+ eventsService.emit("historyService.remove", { removed: result, all: nArray });
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.historyService#removeAll
+ * @methodOf umbraco.services.historyService
+ *
+ * @description
+ * Removes all history items from the users history collection
+ */
+ removeAll: function () {
+ angularHelper.safeApply($rootScope, function() {
+ nArray = [];
+ eventsService.emit("historyService.removeAll");
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.historyService#getCurrent
+ * @methodOf umbraco.services.historyService
+ *
+ * @description
+ * Method to return the current history collection.
+ *
+ */
+ getCurrent: function(){
+ return nArray;
+ }
+ };
+});
+/**
+* @ngdoc service
+* @name umbraco.services.iconHelper
+* @description A helper service for dealing with icons, mostly dealing with legacy tree icons
+**/
+function iconHelper($q, $timeout) {
+
+ var converter = [
+ { oldIcon: ".sprNew", newIcon: "add" },
+ { oldIcon: ".sprDelete", newIcon: "remove" },
+ { oldIcon: ".sprMove", newIcon: "enter" },
+ { oldIcon: ".sprCopy", newIcon: "documents" },
+ { oldIcon: ".sprSort", newIcon: "navigation-vertical" },
+ { oldIcon: ".sprPublish", newIcon: "globe" },
+ { oldIcon: ".sprRollback", newIcon: "undo" },
+ { oldIcon: ".sprProtect", newIcon: "lock" },
+ { oldIcon: ".sprAudit", newIcon: "time" },
+ { oldIcon: ".sprNotify", newIcon: "envelope" },
+ { oldIcon: ".sprDomain", newIcon: "home" },
+ { oldIcon: ".sprPermission", newIcon: "lock" },
+ { oldIcon: ".sprRefresh", newIcon: "refresh" },
+ { oldIcon: ".sprBinEmpty", newIcon: "trash" },
+ { oldIcon: ".sprExportDocumentType", newIcon: "download-alt" },
+ { oldIcon: ".sprImportDocumentType", newIcon: "page-up" },
+ { oldIcon: ".sprLiveEdit", newIcon: "edit" },
+ { oldIcon: ".sprCreateFolder", newIcon: "add" },
+ { oldIcon: ".sprPackage2", newIcon: "box" },
+ { oldIcon: ".sprLogout", newIcon: "logout" },
+ { oldIcon: ".sprSave", newIcon: "save" },
+ { oldIcon: ".sprSendToTranslate", newIcon: "envelope-alt" },
+ { oldIcon: ".sprToPublish", newIcon: "mail-forward" },
+ { oldIcon: ".sprTranslate", newIcon: "comments" },
+ { oldIcon: ".sprUpdate", newIcon: "save" },
+
+ { oldIcon: ".sprTreeSettingDomain", newIcon: "icon-home" },
+ { oldIcon: ".sprTreeDoc", newIcon: "icon-document" },
+ { oldIcon: ".sprTreeDoc2", newIcon: "icon-diploma-alt" },
+ { oldIcon: ".sprTreeDoc3", newIcon: "icon-notepad" },
+ { oldIcon: ".sprTreeDoc4", newIcon: "icon-newspaper-alt" },
+ { oldIcon: ".sprTreeDoc5", newIcon: "icon-notepad-alt" },
+
+ { oldIcon: ".sprTreeDocPic", newIcon: "icon-picture" },
+ { oldIcon: ".sprTreeFolder", newIcon: "icon-folder" },
+ { oldIcon: ".sprTreeFolder_o", newIcon: "icon-folder" },
+ { oldIcon: ".sprTreeMediaFile", newIcon: "icon-music" },
+ { oldIcon: ".sprTreeMediaMovie", newIcon: "icon-movie" },
+ { oldIcon: ".sprTreeMediaPhoto", newIcon: "icon-picture" },
+
+ { oldIcon: ".sprTreeMember", newIcon: "icon-user" },
+ { oldIcon: ".sprTreeMemberGroup", newIcon: "icon-users" },
+ { oldIcon: ".sprTreeMemberType", newIcon: "icon-users" },
+
+ { oldIcon: ".sprTreeNewsletter", newIcon: "icon-file-text-alt" },
+ { oldIcon: ".sprTreePackage", newIcon: "icon-box" },
+ { oldIcon: ".sprTreeRepository", newIcon: "icon-server-alt" },
+
+ { oldIcon: ".sprTreeSettingDataType", newIcon: "icon-autofill" },
+
+ //TODO:
+ /*
+ { oldIcon: ".sprTreeSettingAgent", newIcon: "" },
+ { oldIcon: ".sprTreeSettingCss", newIcon: "" },
+ { oldIcon: ".sprTreeSettingCssItem", newIcon: "" },
+
+ { oldIcon: ".sprTreeSettingDataTypeChild", newIcon: "" },
+ { oldIcon: ".sprTreeSettingDomain", newIcon: "" },
+ { oldIcon: ".sprTreeSettingLanguage", newIcon: "" },
+ { oldIcon: ".sprTreeSettingScript", newIcon: "" },
+ { oldIcon: ".sprTreeSettingTemplate", newIcon: "" },
+ { oldIcon: ".sprTreeSettingXml", newIcon: "" },
+ { oldIcon: ".sprTreeStatistik", newIcon: "" },
+ { oldIcon: ".sprTreeUser", newIcon: "" },
+ { oldIcon: ".sprTreeUserGroup", newIcon: "" },
+ { oldIcon: ".sprTreeUserType", newIcon: "" },
+ */
+
+ { oldIcon: "folder.png", newIcon: "icon-folder" },
+ { oldIcon: "mediaphoto.gif", newIcon: "icon-picture" },
+ { oldIcon: "mediafile.gif", newIcon: "icon-document" },
+
+ { oldIcon: ".sprTreeDeveloperCacheItem", newIcon: "icon-box" },
+ { oldIcon: ".sprTreeDeveloperCacheTypes", newIcon: "icon-box" },
+ { oldIcon: ".sprTreeDeveloperMacro", newIcon: "icon-cogs" },
+ { oldIcon: ".sprTreeDeveloperRegistry", newIcon: "icon-windows" },
+ { oldIcon: ".sprTreeDeveloperPython", newIcon: "icon-linux" }
+ ];
+
+ var imageConverter = [
+ {oldImage: "contour.png", newIcon: "icon-umb-contour"}
+ ];
+
+ var collectedIcons;
+
+ return {
+
+ /** Used by the create dialogs for content/media types to format the data so that the thumbnails are styled properly */
+ formatContentTypeThumbnails: function (contentTypes) {
+ for (var i = 0; i < contentTypes.length; i++) {
+
+ if (contentTypes[i].thumbnailIsClass === undefined || contentTypes[i].thumbnailIsClass) {
+ contentTypes[i].cssClass = this.convertFromLegacyIcon(contentTypes[i].thumbnail);
+ }else {
+ contentTypes[i].style = "background-image: url('" + contentTypes[i].thumbnailFilePath + "');height:36px; background-position:4px 0px; background-repeat: no-repeat;background-size: 35px 35px;";
+ //we need an 'icon-' class in there for certain styles to work so if it is image based we'll add this
+ contentTypes[i].cssClass = "custom-file";
+ }
+ }
+ return contentTypes;
+ },
+ formatContentTypeIcons: function (contentTypes) {
+ for (var i = 0; i < contentTypes.length; i++) {
+ if (!contentTypes[i].icon) {
+ //just to be safe (e.g. when focus was on close link and hitting save)
+ contentTypes[i].icon = "icon-document"; // default icon
+ } else {
+ contentTypes[i].icon = this.convertFromLegacyIcon(contentTypes[i].icon);
+ }
+
+ //couldnt find replacement
+ if(contentTypes[i].icon.indexOf(".") > 0){
+ contentTypes[i].icon = "icon-document-dashed-line";
+ }
+ }
+ return contentTypes;
+ },
+ /** If the icon is file based (i.e. it has a file path) */
+ isFileBasedIcon: function (icon) {
+ //if it doesn't start with a '.' but contains one then we'll assume it's file based
+ if (icon.startsWith('..') || (!icon.startsWith('.') && icon.indexOf('.') > 1)) {
+ return true;
+ }
+ return false;
+ },
+ /** If the icon is legacy */
+ isLegacyIcon: function (icon) {
+ if(!icon) {
+ return false;
+ }
+
+ if(icon.startsWith('..')){
+ return false;
+ }
+
+ if (icon.startsWith('.')) {
+ return true;
+ }
+ return false;
+ },
+ /** If the tree node has a legacy icon */
+ isLegacyTreeNodeIcon: function(treeNode){
+ if (treeNode.iconIsClass) {
+ return this.isLegacyIcon(treeNode.icon);
+ }
+ return false;
+ },
+
+ /** Return a list of icons, optionally filter them */
+ /** It fetches them directly from the active stylesheets in the browser */
+ getIcons: function(){
+ var deferred = $q.defer();
+ $timeout(function(){
+ if(collectedIcons){
+ deferred.resolve(collectedIcons);
+ }else{
+ collectedIcons = [];
+ var c = ".icon-";
+
+ for (var i = document.styleSheets.length - 1; i >= 0; i--) {
+ var classes = document.styleSheets[i].rules || document.styleSheets[i].cssRules;
+
+ if (classes !== null) {
+ for(var x=0;x0){
+ s = s.substring(0, hasSpace);
+ }
+ var hasPseudo = s.indexOf(":");
+ if(hasPseudo>0){
+ s = s.substring(0, hasPseudo);
+ }
+
+ if(collectedIcons.indexOf(s) < 0){
+ collectedIcons.push(s);
+ }
+ }
+ }
+ }
+ }
+ deferred.resolve(collectedIcons);
+ }
+ }, 100);
+
+ return deferred.promise;
+ },
+
+ /** Converts the icon from legacy to a new one if an old one is detected */
+ convertFromLegacyIcon: function (icon) {
+ if (this.isLegacyIcon(icon)) {
+ //its legacy so convert it if we can
+ var found = _.find(converter, function (item) {
+ return item.oldIcon.toLowerCase() === icon.toLowerCase();
+ });
+ return (found ? found.newIcon : icon);
+ }
+ return icon;
+ },
+
+ convertFromLegacyImage: function (icon) {
+ var found = _.find(imageConverter, function (item) {
+ return item.oldImage.toLowerCase() === icon.toLowerCase();
+ });
+ return (found ? found.newIcon : undefined);
+ },
+
+ /** If we detect that the tree node has legacy icons that can be converted, this will convert them */
+ convertFromLegacyTreeNodeIcon: function (treeNode) {
+ if (this.isLegacyTreeNodeIcon(treeNode)) {
+ return this.convertFromLegacyIcon(treeNode.icon);
+ }
+ return treeNode.icon;
+ }
+ };
+}
+angular.module('umbraco.services').factory('iconHelper', iconHelper);
+/**
+* @ngdoc service
+* @name umbraco.services.imageHelper
+* @deprecated
+**/
+function imageHelper(umbRequestHelper, mediaHelper) {
+ return {
+ /**
+ * @ngdoc function
+ * @name umbraco.services.imageHelper#getImagePropertyValue
+ * @methodOf umbraco.services.imageHelper
+ * @function
+ *
+ * @deprecated
+ */
+ getImagePropertyValue: function (options) {
+ return mediaHelper.getImagePropertyValue(options);
+ },
+ /**
+ * @ngdoc function
+ * @name umbraco.services.imageHelper#getThumbnail
+ * @methodOf umbraco.services.imageHelper
+ * @function
+ *
+ * @deprecated
+ */
+ getThumbnail: function (options) {
+ return mediaHelper.getThumbnail(options);
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.imageHelper#scaleToMaxSize
+ * @methodOf umbraco.services.imageHelper
+ * @function
+ *
+ * @deprecated
+ */
+ scaleToMaxSize: function (maxSize, width, height) {
+ return mediaHelper.scaleToMaxSize(maxSize, width, height);
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.imageHelper#getThumbnailFromPath
+ * @methodOf umbraco.services.imageHelper
+ * @function
+ *
+ * @deprecated
+ */
+ getThumbnailFromPath: function (imagePath) {
+ return mediaHelper.getThumbnailFromPath(imagePath);
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.imageHelper#detectIfImageByExtension
+ * @methodOf umbraco.services.imageHelper
+ * @function
+ *
+ * @deprecated
+ */
+ detectIfImageByExtension: function (imagePath) {
+ return mediaHelper.detectIfImageByExtension(imagePath);
+ }
+ };
+}
+angular.module('umbraco.services').factory('imageHelper', imageHelper);
+// This service was based on OpenJS library available in BSD License
+// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php
+
+function keyboardService($window, $timeout) {
+
+ var keyboardManagerService = {};
+
+ var defaultOpt = {
+ 'type': 'keydown',
+ 'propagate': false,
+ 'inputDisabled': false,
+ 'target': $window.document,
+ 'keyCode': false
+ };
+
+ // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
+ var shift_nums = {
+ "`": "~",
+ "1": "!",
+ "2": "@",
+ "3": "#",
+ "4": "$",
+ "5": "%",
+ "6": "^",
+ "7": "&",
+ "8": "*",
+ "9": "(",
+ "0": ")",
+ "-": "_",
+ "=": "+",
+ ";": ":",
+ "'": "\"",
+ ",": "<",
+ ".": ">",
+ "/": "?",
+ "\\": "|"
+ };
+
+ // Special Keys - and their codes
+ var special_keys = {
+ 'esc': 27,
+ 'escape': 27,
+ 'tab': 9,
+ 'space': 32,
+ 'return': 13,
+ 'enter': 13,
+ 'backspace': 8,
+
+ 'scrolllock': 145,
+ 'scroll_lock': 145,
+ 'scroll': 145,
+ 'capslock': 20,
+ 'caps_lock': 20,
+ 'caps': 20,
+ 'numlock': 144,
+ 'num_lock': 144,
+ 'num': 144,
+
+ 'pause': 19,
+ 'break': 19,
+
+ 'insert': 45,
+ 'home': 36,
+ 'delete': 46,
+ 'end': 35,
+
+ 'pageup': 33,
+ 'page_up': 33,
+ 'pu': 33,
+
+ 'pagedown': 34,
+ 'page_down': 34,
+ 'pd': 34,
+
+ 'left': 37,
+ 'up': 38,
+ 'right': 39,
+ 'down': 40,
+
+ 'f1': 112,
+ 'f2': 113,
+ 'f3': 114,
+ 'f4': 115,
+ 'f5': 116,
+ 'f6': 117,
+ 'f7': 118,
+ 'f8': 119,
+ 'f9': 120,
+ 'f10': 121,
+ 'f11': 122,
+ 'f12': 123
+ };
+
+ var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0;
+
+ // The event handler for bound element events
+ function eventHandler(e) {
+ e = e || $window.event;
+
+ var code, k;
+
+ // Find out which key is pressed
+ if (e.keyCode)
+ {
+ code = e.keyCode;
+ }
+ else if (e.which) {
+ code = e.which;
+ }
+
+ var character = String.fromCharCode(code).toLowerCase();
+
+ if (code === 188){character = ",";} // If the user presses , when the type is onkeydown
+ if (code === 190){character = ".";} // If the user presses , when the type is onkeydown
+
+ var propagate = true;
+
+ //Now we need to determine which shortcut this event is for, we'll do this by iterating over each
+ //registered shortcut to find the match. We use Find here so that the loop exits as soon
+ //as we've found the one we're looking for
+ _.find(_.keys(keyboardManagerService.keyboardEvent), function(key) {
+
+ var shortcutLabel = key;
+ var shortcutVal = keyboardManagerService.keyboardEvent[key];
+
+ // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
+ var kp = 0;
+
+ // Some modifiers key
+ var modifiers = {
+ shift: {
+ wanted: false,
+ pressed: e.shiftKey ? true : false
+ },
+ ctrl: {
+ wanted: false,
+ pressed: e.ctrlKey ? true : false
+ },
+ alt: {
+ wanted: false,
+ pressed: e.altKey ? true : false
+ },
+ meta: { //Meta is Mac specific
+ wanted: false,
+ pressed: e.metaKey ? true : false
+ }
+ };
+
+ var keys = shortcutLabel.split("+");
+ var opt = shortcutVal.opt;
+ var callback = shortcutVal.callback;
+
+ // Foreach keys in label (split on +)
+ var l = keys.length;
+ for (var i = 0; i < l; i++) {
+
+ var k = keys[i];
+ switch (k) {
+ case 'ctrl':
+ case 'control':
+ kp++;
+ modifiers.ctrl.wanted = true;
+ break;
+ case 'shift':
+ case 'alt':
+ case 'meta':
+ kp++;
+ modifiers[k].wanted = true;
+ break;
+ }
+
+ if (k.length > 1) { // If it is a special key
+ if (special_keys[k] === code) {
+ kp++;
+ }
+ }
+ else if (opt['keyCode']) { // If a specific key is set into the config
+ if (opt['keyCode'] === code) {
+ kp++;
+ }
+ }
+ else { // The special keys did not match
+ if (character === k) {
+ kp++;
+ }
+ else {
+ if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase
+ character = shift_nums[character];
+ if (character === k) {
+ kp++;
+ }
+ }
+ }
+ }
+
+ } //for end
+
+ if (kp === keys.length &&
+ modifiers.ctrl.pressed === modifiers.ctrl.wanted &&
+ modifiers.shift.pressed === modifiers.shift.wanted &&
+ modifiers.alt.pressed === modifiers.alt.wanted &&
+ modifiers.meta.pressed === modifiers.meta.wanted) {
+
+ //found the right callback!
+
+ // Disable event handler when focus input and textarea
+ if (opt['inputDisabled']) {
+ var elt;
+ if (e.target) {
+ elt = e.target;
+ } else if (e.srcElement) {
+ elt = e.srcElement;
+ }
+
+ if (elt.nodeType === 3) { elt = elt.parentNode; }
+ if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA') {
+ //This exits the Find loop
+ return true;
+ }
+ }
+
+ $timeout(function () {
+ callback(e);
+ }, 1);
+
+ if (!opt['propagate']) { // Stop the event
+ propagate = false;
+ }
+
+ //This exits the Find loop
+ return true;
+ }
+
+ //we haven't found one so continue looking
+ return false;
+
+ });
+
+ // Stop the event if required
+ if (!propagate) {
+ // e.cancelBubble is supported by IE - this will kill the bubbling process.
+ e.cancelBubble = true;
+ e.returnValue = false;
+
+ // e.stopPropagation works in Firefox.
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ return false;
+ }
+ }
+
+ // Store all keyboard combination shortcuts
+ keyboardManagerService.keyboardEvent = {};
+
+ // Add a new keyboard combination shortcut
+ keyboardManagerService.bind = function (label, callback, opt) {
+
+ //replace ctrl key with meta key
+ if(isMac && label !== "ctrl+space"){
+ label = label.replace("ctrl","meta");
+ }
+
+ var elt;
+ // Initialize opt object
+ opt = angular.extend({}, defaultOpt, opt);
+ label = label.toLowerCase();
+ elt = opt.target;
+ if(typeof opt.target === 'string'){
+ elt = document.getElementById(opt.target);
+ }
+
+ //Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding
+ // and raising events for now reason. So here we'll check if the event is already registered for the element
+ var boundValues = _.values(keyboardManagerService.keyboardEvent);
+ var found = _.find(boundValues, function (i) {
+ return i.target === elt && i.event === opt['type'];
+ });
+
+ // Store shortcut
+ keyboardManagerService.keyboardEvent[label] = {
+ 'callback': callback,
+ 'target': elt,
+ 'opt': opt
+ };
+
+ if (!found) {
+ //Attach the function with the event
+ if (elt.addEventListener) {
+ elt.addEventListener(opt['type'], eventHandler, false);
+ } else if (elt.attachEvent) {
+ elt.attachEvent('on' + opt['type'], eventHandler);
+ } else {
+ elt['on' + opt['type']] = eventHandler;
+ }
+ }
+
+ };
+ // Remove the shortcut - just specify the shortcut and I will remove the binding
+ keyboardManagerService.unbind = function (label) {
+ label = label.toLowerCase();
+ var binding = keyboardManagerService.keyboardEvent[label];
+ delete(keyboardManagerService.keyboardEvent[label]);
+
+ if(!binding){return;}
+
+ var type = binding['event'],
+ elt = binding['target'],
+ callback = binding['callback'];
+
+ if(elt.detachEvent){
+ elt.detachEvent('on' + type, callback);
+ }else if(elt.removeEventListener){
+ elt.removeEventListener(type, callback, false);
+ }else{
+ elt['on'+type] = false;
+ }
+ };
+ //
+
+ return keyboardManagerService;
+}
angular.module('umbraco.services').factory('keyboardService', ['$window', '$timeout', keyboardService]);
+/**
+ @ngdoc service
+ * @name umbraco.services.listViewHelper
+ *
+ *
+ * @description
+ * Service for performing operations against items in the list view UI. Used by the built-in internal listviews
+ * as well as custom listview.
+ *
+ * A custom listview is always used inside a wrapper listview, so there are a number of inherited values on its
+ * scope by default:
+ *
+ * **$scope.selection**: Array containing all items currently selected in the listview
+ *
+ * **$scope.items**: Array containing all items currently displayed in the listview
+ *
+ * **$scope.folders**: Array containing all folders in the current listview (only for media)
+ *
+ * **$scope.options**: configuration object containing information such as pagesize, permissions, order direction etc.
+ *
+ * **$scope.model.config.layouts**: array of available layouts to apply to the listview (grid, list or custom layout)
+ *
+ * ##Usage##
+ * To use, inject listViewHelper into custom listview controller, listviewhelper expects you
+ * to pass in the full collection of items in the listview in several of its methods
+ * this collection is inherited from the parent controller and is available on $scope.selection
+ *
+ *
+ * angular.module("umbraco").controller("my.listVieweditor". function($scope, listViewHelper){
+ *
+ * //current items in the listview
+ * var items = $scope.items;
+ *
+ * //current selection
+ * var selection = $scope.selection;
+ *
+ * //deselect an item , $scope.selection is inherited, item is picked from inherited $scope.items
+ * listViewHelper.deselectItem(item, $scope.selection);
+ *
+ * //test if all items are selected, $scope.items + $scope.selection are inherited
+ * listViewhelper.isSelectedAll($scope.items, $scope.selection);
+ * });
+ *
+ */
+(function () {
+ 'use strict';
+
+ function listViewHelper(localStorageService) {
+
+ var firstSelectedIndex = 0;
+ var localStorageKey = "umblistViewLayout";
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#getLayout
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Method for internal use, based on the collection of layouts passed, the method selects either
+ * any previous layout from local storage, or picks the first allowed layout
+ *
+ * @param {Number} nodeId The id of the current node displayed in the content editor
+ * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts
+ */
+
+ function getLayout(nodeId, availableLayouts) {
+
+ var storedLayouts = [];
+
+ if (localStorageService.get(localStorageKey)) {
+ storedLayouts = localStorageService.get(localStorageKey);
+ }
+
+ if (storedLayouts && storedLayouts.length > 0) {
+ for (var i = 0; storedLayouts.length > i; i++) {
+ var layout = storedLayouts[i];
+ if (layout.nodeId === nodeId) {
+ return setLayout(nodeId, layout, availableLayouts);
+ }
+ }
+
+ }
+
+ return getFirstAllowedLayout(availableLayouts);
+
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#setLayout
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage
+ *
+ * @param {Number} nodeID Id of the current node displayed in the content editor
+ * @param {Object} selectedLayout Layout selected as the layout to set as the current layout
+ * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts
+ */
+
+ function setLayout(nodeId, selectedLayout, availableLayouts) {
+
+ var activeLayout = {};
+ var layoutFound = false;
+
+ for (var i = 0; availableLayouts.length > i; i++) {
+ var layout = availableLayouts[i];
+ if (layout.path === selectedLayout.path) {
+ activeLayout = layout;
+ layout.active = true;
+ layoutFound = true;
+ } else {
+ layout.active = false;
+ }
+ }
+
+ if (!layoutFound) {
+ activeLayout = getFirstAllowedLayout(availableLayouts);
+ }
+
+ saveLayoutInLocalStorage(nodeId, activeLayout);
+
+ return activeLayout;
+
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#saveLayoutInLocalStorage
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Stores a given layout as the current default selection in local storage
+ *
+ * @param {Number} nodeId Id of the current node displayed in the content editor
+ * @param {Object} selectedLayout Layout selected as the layout to set as the current layout
+ */
+
+ function saveLayoutInLocalStorage(nodeId, selectedLayout) {
+ var layoutFound = false;
+ var storedLayouts = [];
+
+ if (localStorageService.get(localStorageKey)) {
+ storedLayouts = localStorageService.get(localStorageKey);
+ }
+
+ if (storedLayouts.length > 0) {
+ for (var i = 0; storedLayouts.length > i; i++) {
+ var layout = storedLayouts[i];
+ if (layout.nodeId === nodeId) {
+ layout.path = selectedLayout.path;
+ layoutFound = true;
+ }
+ }
+ }
+
+ if (!layoutFound) {
+ var storageObject = {
+ "nodeId": nodeId,
+ "path": selectedLayout.path
+ };
+ storedLayouts.push(storageObject);
+ }
+
+ localStorageService.set(localStorageKey, storedLayouts);
+
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#getFirstAllowedLayout
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Returns currently selected layout, or alternatively the first layout in the available layouts collection
+ *
+ * @param {Array} layouts Array of all allowed layouts, available from $scope.model.config.layouts
+ */
+
+ function getFirstAllowedLayout(layouts) {
+
+ var firstAllowedLayout = {};
+
+ for (var i = 0; layouts.length > i; i++) {
+ var layout = layouts[i];
+ if (layout.selected === true) {
+ firstAllowedLayout = layout;
+ break;
+ }
+ }
+
+ return firstAllowedLayout;
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#selectHandler
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Helper method for working with item selection via a checkbox, internally it uses selectItem and deselectItem.
+ * Working with this method, requires its triggered via a checkbox which can then pass in its triggered $event
+ * When the checkbox is clicked, this method will toggle selection of the associated item so it matches the state of the checkbox
+ *
+ * @param {Object} selectedItem Item being selected or deselected by the checkbox
+ * @param {Number} selectedIndex Index of item being selected/deselected, usually passed as $index
+ * @param {Array} items All items in the current listview, available as $scope.items
+ * @param {Array} selection All selected items in the current listview, available as $scope.selection
+ * @param {Event} $event Event triggered by the checkbox being checked to select / deselect an item
+ */
+
+ function selectHandler(selectedItem, selectedIndex, items, selection, $event) {
+
+ var start = 0;
+ var end = 0;
+ var item = null;
+
+ if ($event.shiftKey === true) {
+
+ if (selectedIndex > firstSelectedIndex) {
+
+ start = firstSelectedIndex;
+ end = selectedIndex;
+
+ for (; end >= start; start++) {
+ item = items[start];
+ selectItem(item, selection);
+ }
+
+ } else {
+
+ start = firstSelectedIndex;
+ end = selectedIndex;
+
+ for (; end <= start; start--) {
+ item = items[start];
+ selectItem(item, selection);
+ }
+
+ }
+
+ } else {
+
+ if (selectedItem.selected) {
+ deselectItem(selectedItem, selection);
+ } else {
+ selectItem(selectedItem, selection);
+ }
+
+ firstSelectedIndex = selectedIndex;
+
+ }
+
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#selectItem
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Selects a given item to the listview selection array, requires you pass in the inherited $scope.selection collection
+ *
+ * @param {Object} item Item to select
+ * @param {Array} selection Listview selection, available as $scope.selection
+ */
+
+ function selectItem(item, selection) {
+ var isSelected = false;
+ for (var i = 0; selection.length > i; i++) {
+ var selectedItem = selection[i];
+ if (item.id === selectedItem.id) {
+ isSelected = true;
+ }
+ }
+ if (!isSelected) {
+ selection.push({ id: item.id });
+ item.selected = true;
+ }
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#deselectItem
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Deselects a given item from the listviews selection array, requires you pass in the inherited $scope.selection collection
+ *
+ * @param {Object} item Item to deselect
+ * @param {Array} selection Listview selection, available as $scope.selection
+ */
+
+ function deselectItem(item, selection) {
+ for (var i = 0; selection.length > i; i++) {
+ var selectedItem = selection[i];
+ if (item.id === selectedItem.id) {
+ selection.splice(i, 1);
+ item.selected = false;
+ }
+ }
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#clearSelection
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Removes a given number of items and folders from the listviews selection array
+ * Folders can only be passed in if the listview is used in the media section which has a concept of folders.
+ *
+ * @param {Array} items Items to remove, can be null
+ * @param {Array} folders Folders to remove, can be null
+ * @param {Array} selection Listview selection, available as $scope.selection
+ */
+
+ function clearSelection(items, folders, selection) {
+
+ var i = 0;
+
+ selection.length = 0;
+
+ if (angular.isArray(items)) {
+ for (i = 0; items.length > i; i++) {
+ var item = items[i];
+ item.selected = false;
+ }
+ }
+
+ if(angular.isArray(folders)) {
+ for (i = 0; folders.length > i; i++) {
+ var folder = folders[i];
+ folder.selected = false;
+ }
+ }
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#selectAllItems
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Helper method for toggling the select state on all items in the active listview
+ * Can only be used from a checkbox as a checkbox $event is required to pass in.
+ *
+ * @param {Array} items Items to toggle selection on, should be $scope.items
+ * @param {Array} selection Listview selection, available as $scope.selection
+ * @param {$event} $event Event passed from the checkbox being toggled
+ */
+
+ function selectAllItems(items, selection, $event) {
+
+ var checkbox = $event.target;
+ var clearSelection = false;
+
+ if (!angular.isArray(items)) {
+ return;
+ }
+
+ selection.length = 0;
+
+ for (var i = 0; i < items.length; i++) {
+
+ var item = items[i];
+
+ if (checkbox.checked) {
+ selection.push({ id: item.id });
+ } else {
+ clearSelection = true;
+ }
+
+ item.selected = checkbox.checked;
+
+ }
+
+ if (clearSelection) {
+ selection.length = 0;
+ }
+
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#isSelectedAll
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Method to determine if all items on the current page in the list has been selected
+ * Given the current items in the view, and the current selection, it will return true/false
+ *
+ * @param {Array} items Items to test if all are selected, should be $scope.items
+ * @param {Array} selection Listview selection, available as $scope.selection
+ * @returns {Boolean} boolean indicate if all items in the listview have been selected
+ */
+
+ function isSelectedAll(items, selection) {
+
+ var numberOfSelectedItem = 0;
+
+ for (var itemIndex = 0; items.length > itemIndex; itemIndex++) {
+ var item = items[itemIndex];
+
+ for (var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) {
+ var selectedItem = selection[selectedIndex];
+
+ if (item.id === selectedItem.id) {
+ numberOfSelectedItem++;
+ }
+ }
+
+ }
+
+ if (numberOfSelectedItem === items.length) {
+ return true;
+ }
+
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#setSortingDirection
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * *Internal* method for changing sort order icon
+ * @param {String} col Column alias to order after
+ * @param {String} direction Order direction `asc` or `desc`
+ * @param {Object} options object passed from the parent listview available as $scope.options
+ */
+
+ function setSortingDirection(col, direction, options) {
+ return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction;
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewHelper#setSorting
+ * @methodOf umbraco.services.listViewHelper
+ *
+ * @description
+ * Method for setting the field on which the listview will order its items after.
+ *
+ * @param {String} field Field alias to order after
+ * @param {Boolean} allow Determines if the user is allowed to set this field, normally true
+ * @param {Object} options Options object passed from the parent listview available as $scope.options
+ */
+
+ function setSorting(field, allow, options) {
+ if (allow) {
+ if (options.orderBy === field && options.orderDirection === 'asc') {
+ options.orderDirection = "desc";
+ } else {
+ options.orderDirection = "asc";
+ }
+ options.orderBy = field;
+ }
+ }
+
+ //This takes in a dictionary of Ids with Permissions and determines
+ // the intersect of all permissions to return an object representing the
+ // listview button permissions
+ function getButtonPermissions(unmergedPermissions, currentIdsWithPermissions) {
+
+ if (currentIdsWithPermissions == null) {
+ currentIdsWithPermissions = {};
+ }
+
+ //merge the newly retrieved permissions to the main dictionary
+ _.each(unmergedPermissions, function (value, key, list) {
+ currentIdsWithPermissions[key] = value;
+ });
+
+ //get the intersect permissions
+ var arr = [];
+ _.each(currentIdsWithPermissions, function (value, key, list) {
+ arr.push(value);
+ });
+
+ //we need to use 'apply' to call intersection with an array of arrays,
+ //see: http://stackoverflow.com/a/16229480/694494
+ var intersectPermissions = _.intersection.apply(_, arr);
+
+ return {
+ canCopy: _.contains(intersectPermissions, 'O'), //Magic Char = O
+ canCreate: _.contains(intersectPermissions, 'C'), //Magic Char = C
+ canDelete: _.contains(intersectPermissions, 'D'), //Magic Char = D
+ canMove: _.contains(intersectPermissions, 'M'), //Magic Char = M
+ canPublish: _.contains(intersectPermissions, 'U'), //Magic Char = U
+ canUnpublish: _.contains(intersectPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish)
+ };
+ }
+
+ var service = {
+
+ getLayout: getLayout,
+ getFirstAllowedLayout: getFirstAllowedLayout,
+ setLayout: setLayout,
+ saveLayoutInLocalStorage: saveLayoutInLocalStorage,
+ selectHandler: selectHandler,
+ selectItem: selectItem,
+ deselectItem: deselectItem,
+ clearSelection: clearSelection,
+ selectAllItems: selectAllItems,
+ isSelectedAll: isSelectedAll,
+ setSortingDirection: setSortingDirection,
+ setSorting: setSorting,
+ getButtonPermissions: getButtonPermissions
+
+ };
+
+ return service;
+
+ }
+
+
+ angular.module('umbraco.services').factory('listViewHelper', listViewHelper);
+
+
+})();
+
+/**
+ @ngdoc service
+ * @name umbraco.services.listViewPrevalueHelper
+ *
+ *
+ * @description
+ * Service for accessing the prevalues of a list view being edited in the inline list view editor in the doctype editor
+ */
+(function () {
+ 'use strict';
+
+ function listViewPrevalueHelper() {
+
+ var prevalues = [];
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewPrevalueHelper#getPrevalues
+ * @methodOf umbraco.services.listViewPrevalueHelper
+ *
+ * @description
+ * Set the collection of prevalues
+ */
+
+ function getPrevalues() {
+ return prevalues;
+ }
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.listViewPrevalueHelper#setPrevalues
+ * @methodOf umbraco.services.listViewPrevalueHelper
+ *
+ * @description
+ * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage
+ *
+ * @param {Array} values Array of prevalues
+ */
+
+ function setPrevalues(values) {
+ prevalues = values;
+ }
+
+
+
+ var service = {
+
+ getPrevalues: getPrevalues,
+ setPrevalues: setPrevalues
+
+ };
+
+ return service;
+
+ }
+
+
+ angular.module('umbraco.services').factory('listViewPrevalueHelper', listViewPrevalueHelper);
+
+
+})();
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.localizationService
+ *
+ * @requires $http
+ * @requires $q
+ * @requires $window
+ * @requires $filter
+ *
+ * @description
+ * Application-wide service for handling localization
+ *
+ * ##usage
+ * To use, simply inject the localizationService into any controller that needs it, and make
+ * sure the umbraco.services module is accesible - which it should be by default.
+ *
+ *
+ */
+
+angular.module('umbraco.services')
+.factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) {
+
+ //TODO: This should be injected as server vars
+ var url = "LocalizedText";
+ var resourceFileLoadStatus = "none";
+ var resourceLoadingPromise = [];
+
+ function _lookup(value, tokens, dictionary) {
+
+ //strip the key identifier if its there
+ if (value && value[0] === "@") {
+ value = value.substring(1);
+ }
+
+ //if no area specified, add general_
+ if (value && value.indexOf("_") < 0) {
+ value = "general_" + value;
+ }
+
+ var entry = dictionary[value];
+ if (entry) {
+ if (tokens) {
+ for (var i = 0; i < tokens.length; i++) {
+ entry = entry.replace("%" + i + "%", tokens[i]);
+ }
+ }
+ return entry;
+ }
+ return "[" + value + "]";
+ }
+
+ var service = {
+ // array to hold the localized resource string entries
+ dictionary: [],
+
+ // loads the language resource file from the server
+ initLocalizedResources: function () {
+ var deferred = $q.defer();
+
+ if (resourceFileLoadStatus === "loaded") {
+ deferred.resolve(service.dictionary);
+ return deferred.promise;
+ }
+
+ //if the resource is already loading, we don't want to force it to load another one in tandem, we'd rather
+ // wait for that initial http promise to finish and then return this one with the dictionary loaded
+ if (resourceFileLoadStatus === "loading") {
+ //add to the list of promises waiting
+ resourceLoadingPromise.push(deferred);
+
+ //exit now it's already loading
+ return deferred.promise;
+ }
+
+ resourceFileLoadStatus = "loading";
+
+ // build the url to retrieve the localized resource file
+ $http({ method: "GET", url: url, cache: false })
+ .then(function (response) {
+ resourceFileLoadStatus = "loaded";
+ service.dictionary = response.data;
+
+ eventsService.emit("localizationService.updated", response.data);
+
+ deferred.resolve(response.data);
+ //ensure all other queued promises are resolved
+ for (var p in resourceLoadingPromise) {
+ resourceLoadingPromise[p].resolve(response.data);
+ }
+ }, function (err) {
+ deferred.reject("Something broke");
+ //ensure all other queued promises are resolved
+ for (var p in resourceLoadingPromise) {
+ resourceLoadingPromise[p].reject("Something broke");
+ }
+ });
+ return deferred.promise;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.localizationService#tokenize
+ * @methodOf umbraco.services.localizationService
+ *
+ * @description
+ * Helper to tokenize and compile a localization string
+ * @param {String} value the value to tokenize
+ * @param {Object} scope the $scope object
+ * @returns {String} tokenized resource string
+ */
+ tokenize: function (value, scope) {
+ if (value) {
+ var localizer = value.split(':');
+ var retval = { tokens: undefined, key: localizer[0].substring(0) };
+ if (localizer.length > 1) {
+ retval.tokens = localizer[1].split(',');
+ for (var x = 0; x < retval.tokens.length; x++) {
+ retval.tokens[x] = scope.$eval(retval.tokens[x]);
+ }
+ }
+
+ return retval;
+ }
+ return value;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.localizationService#localize
+ * @methodOf umbraco.services.localizationService
+ *
+ * @description
+ * Checks the dictionary for a localized resource string
+ * @param {String} value the area/key to localize
+ * @param {Array} tokens if specified this array will be sent as parameter values
+ * @returns {String} localized resource string
+ */
+ localize: function (value, tokens) {
+ return service.initLocalizedResources().then(function (dic) {
+ var val = _lookup(value, tokens, dic);
+ return val;
+ });
+ },
+
+ };
+
+ //This happens after login / auth and assets loading
+ eventsService.on("app.authenticated", function () {
+ resourceFileLoadStatus = "none";
+ resourceLoadingPromise = [];
+ });
+
+ // return the local instance when called
+ return service;
+});
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.macroService
+ *
+ *
+ * @description
+ * A service to return macro information such as generating syntax to insert a macro into an editor
+ */
+function macroService() {
+
+ return {
+
+ /** parses the special macro syntax like and returns an object with the macro alias and it's parameters */
+ parseMacroSyntax: function (syntax) {
+
+ //This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created
+ // their aliases are cleaned an invalid chars are stripped)
+ var expression = /(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i;
+ var match = expression.exec(syntax);
+ if (!match || match.length < 3) {
+ return null;
+ }
+ var alias = match[2];
+
+ //this will leave us with just the parameters
+ var paramsChunk = match[1].trim().replace(new RegExp("UMBRACO_MACRO macroAlias=[\"']" + alias + "[\"']"), "").trim();
+
+ var paramExpression = /(\w+?)=['\"]([\s\S]*?)['\"]/g;
+
+ var paramMatch;
+ var returnVal = {
+ macroAlias: alias,
+ macroParamsDictionary: {}
+ };
+ while (paramMatch = paramExpression.exec(paramsChunk)) {
+ returnVal.macroParamsDictionary[paramMatch[1]] = paramMatch[2];
+ }
+ return returnVal;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.macroService#generateWebFormsSyntax
+ * @methodOf umbraco.services.macroService
+ * @function
+ *
+ * @description
+ * generates the syntax for inserting a macro into a rich text editor - this is the very old umbraco style syntax
+ *
+ * @param {object} args an object containing the macro alias and it's parameter values
+ */
+ generateMacroSyntax: function (args) {
+
+ //
+
+ var macroString = '";
+
+ return macroString;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.macroService#generateWebFormsSyntax
+ * @methodOf umbraco.services.macroService
+ * @function
+ *
+ * @description
+ * generates the syntax for inserting a macro into a webforms templates
+ *
+ * @param {object} args an object containing the macro alias and it's parameter values
+ */
+ generateWebFormsSyntax: function(args) {
+
+ var macroString = '";
+
+ return macroString;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.macroService#generateMvcSyntax
+ * @methodOf umbraco.services.macroService
+ * @function
+ *
+ * @description
+ * generates the syntax for inserting a macro into an mvc template
+ *
+ * @param {object} args an object containing the macro alias and it's parameter values
+ */
+ generateMvcSyntax: function (args) {
+
+ var macroString = "@Umbraco.RenderMacro(\"" + args.macroAlias + "\"";
+
+ var hasParams = false;
+ var paramString;
+ if (args.macroParamsDictionary) {
+
+ paramString = ", new {";
+
+ _.each(args.macroParamsDictionary, function(val, key) {
+
+ hasParams = true;
+
+ var keyVal = key + "=\"" + (val ? val : "") + "\", ";
+
+ paramString += keyVal;
+ });
+
+ //remove the last ,
+ paramString = paramString.trimEnd(", ");
+
+ paramString += "}";
+ }
+ if (hasParams) {
+ macroString += paramString;
+ }
+
+ macroString += ")";
+ return macroString;
+ },
+
+ collectValueData: function(macro, macroParams, renderingEngine) {
+
+ var paramDictionary = {};
+ var macroAlias = macro.alias;
+ var syntax;
+
+ _.each(macroParams, function (item) {
+
+ var val = item.value;
+
+ if (item.value !== null && item.value !== undefined && !_.isString(item.value)) {
+ try {
+ val = angular.toJson(val);
+ }
+ catch (e) {
+ // not json
+ }
+ }
+
+ //each value needs to be xml escaped!! since the value get's stored as an xml attribute
+ paramDictionary[item.alias] = _.escape(val);
+
+ });
+
+ //get the syntax based on the rendering engine
+ if (renderingEngine && renderingEngine === "WebForms") {
+ syntax = this.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
+ }
+ else if (renderingEngine && renderingEngine === "Mvc") {
+ syntax = this.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
+ }
+ else {
+ syntax = this.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
+ }
+
+ var macroObject = {
+ "macroParamsDictionary": paramDictionary,
+ "macroAlias": macroAlias,
+ "syntax": syntax
+ };
+
+ return macroObject;
+
+ }
+
+ };
+
+}
+
+angular.module('umbraco.services').factory('macroService', macroService);
+
+/**
+* @ngdoc service
+* @name umbraco.services.mediaHelper
+* @description A helper object used for dealing with media items
+**/
+function mediaHelper(umbRequestHelper) {
+
+ //container of fileresolvers
+ var _mediaFileResolvers = {};
+
+ return {
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#getImagePropertyValue
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns the file path associated with the media property if there is one
+ *
+ * @param {object} options Options object
+ * @param {object} options.mediaModel The media object to retrieve the image path from
+ * @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image
+ */
+ getMediaPropertyValue: function (options) {
+ if (!options || !options.mediaModel) {
+ throw "The options objet does not contain the required parameters: mediaModel";
+ }
+
+ //combine all props, TODO: we really need a better way then this
+ var props = [];
+ if (options.mediaModel.properties) {
+ props = options.mediaModel.properties;
+ } else {
+ $(options.mediaModel.tabs).each(function (i, tab) {
+ props = props.concat(tab.properties);
+ });
+ }
+
+ var mediaRoot = Umbraco.Sys.ServerVariables.umbracoSettings.mediaPath;
+ var imageProp = _.find(props, function (item) {
+ if (item.alias === "umbracoFile") {
+ return true;
+ }
+
+ //this performs a simple check to see if we have a media file as value
+ //it doesnt catch everything, but better then nothing
+ if (angular.isString(item.value) && item.value.indexOf(mediaRoot) === 0) {
+ return true;
+ }
+
+ return false;
+ });
+
+ if (!imageProp) {
+ return "";
+ }
+
+ var mediaVal;
+
+ //our default images might store one or many images (as csv)
+ var split = imageProp.value.split(',');
+ var self = this;
+ mediaVal = _.map(split, function (item) {
+ return { file: item, isImage: self.detectIfImageByExtension(item) };
+ });
+
+ //for now we'll just return the first image in the collection.
+ //TODO: we should enable returning many to be displayed in the picker if the uploader supports many.
+ if (mediaVal.length && mediaVal.length > 0) {
+ if (!options.imageOnly || (options.imageOnly === true && mediaVal[0].isImage)) {
+ return mediaVal[0].file;
+ }
+ }
+
+ return "";
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#getImagePropertyValue
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns the actual image path associated with the image property if there is one
+ *
+ * @param {object} options Options object
+ * @param {object} options.imageModel The media object to retrieve the image path from
+ */
+ getImagePropertyValue: function (options) {
+ if (!options || (!options.imageModel && !options.mediaModel)) {
+ throw "The options objet does not contain the required parameters: imageModel";
+ }
+
+ //required to support backwards compatibility.
+ options.mediaModel = options.imageModel ? options.imageModel : options.mediaModel;
+
+ options.imageOnly = true;
+
+ return this.getMediaPropertyValue(options);
+ },
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#getThumbnail
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * formats the display model used to display the content to the model used to save the content
+ *
+ * @param {object} options Options object
+ * @param {object} options.imageModel The media object to retrieve the image path from
+ */
+ getThumbnail: function (options) {
+
+ if (!options || !options.imageModel) {
+ throw "The options objet does not contain the required parameters: imageModel";
+ }
+
+ var imagePropVal = this.getImagePropertyValue(options);
+ if (imagePropVal !== "") {
+ return this.getThumbnailFromPath(imagePropVal);
+ }
+ return "";
+ },
+
+ registerFileResolver: function(propertyEditorAlias, func){
+ _mediaFileResolvers[propertyEditorAlias] = func;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#resolveFileFromEntity
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Gets the media file url for a media entity returned with the entityResource
+ *
+ * @param {object} mediaEntity A media Entity returned from the entityResource
+ * @param {boolean} thumbnail Whether to return the thumbnail url or normal url
+ */
+ resolveFileFromEntity : function(mediaEntity, thumbnail) {
+
+ if (!angular.isObject(mediaEntity.metaData)) {
+ throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData";
+ }
+
+ var values = _.values(mediaEntity.metaData);
+ for (var i = 0; i < values.length; i++) {
+ var val = values[i];
+ if (angular.isObject(val) && val.PropertyEditorAlias) {
+ for (var resolver in _mediaFileResolvers) {
+ if (val.PropertyEditorAlias === resolver) {
+ //we need to format a property variable that coincides with how the property would be structured
+ // if it came from the mediaResource just to keep things slightly easier for the file resolvers.
+ var property = { value: val.Value };
+
+ return _mediaFileResolvers[resolver](property, mediaEntity, thumbnail);
+ }
+ }
+ }
+ }
+
+ return "";
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#resolveFile
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Gets the media file url for a media object returned with the mediaResource
+ *
+ * @param {object} mediaEntity A media Entity returned from the entityResource
+ * @param {boolean} thumbnail Whether to return the thumbnail url or normal url
+ */
+ /*jshint loopfunc: true */
+ resolveFile : function(mediaItem, thumbnail){
+
+ function iterateProps(props){
+ var res = null;
+ for(var resolver in _mediaFileResolvers) {
+ var property = _.find(props, function(prop){ return prop.editor === resolver; });
+ if(property){
+ res = _mediaFileResolvers[resolver](property, mediaItem, thumbnail);
+ break;
+ }
+ }
+
+ return res;
+ }
+
+ //we either have properties raw on the object, or spread out on tabs
+ var result = "";
+ if(mediaItem.properties){
+ result = iterateProps(mediaItem.properties);
+ }else if(mediaItem.tabs){
+ for(var tab in mediaItem.tabs) {
+ if(mediaItem.tabs[tab].properties){
+ result = iterateProps(mediaItem.tabs[tab].properties);
+ if(result){
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ },
+
+ /*jshint loopfunc: true */
+ hasFilePropertyType : function(mediaItem){
+ function iterateProps(props){
+ var res = false;
+ for(var resolver in _mediaFileResolvers) {
+ var property = _.find(props, function(prop){ return prop.editor === resolver; });
+ if(property){
+ res = true;
+ break;
+ }
+ }
+ return res;
+ }
+
+ //we either have properties raw on the object, or spread out on tabs
+ var result = false;
+ if(mediaItem.properties){
+ result = iterateProps(mediaItem.properties);
+ }else if(mediaItem.tabs){
+ for(var tab in mediaItem.tabs) {
+ if(mediaItem.tabs[tab].properties){
+ result = iterateProps(mediaItem.tabs[tab].properties);
+ if(result){
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#scaleToMaxSize
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios
+ *
+ * @param {number} maxSize Maximum width & height
+ * @param {number} width Current width
+ * @param {number} height Current height
+ */
+ scaleToMaxSize: function (maxSize, width, height) {
+ var retval = { width: width, height: height };
+
+ var maxWidth = maxSize; // Max width for the image
+ var maxHeight = maxSize; // Max height for the image
+ var ratio = 0; // Used for aspect ratio
+
+ // Check if the current width is larger than the max
+ if (width > maxWidth) {
+ ratio = maxWidth / width; // get ratio for scaling image
+
+ retval.width = maxWidth;
+ retval.height = height * ratio;
+
+ height = height * ratio; // Reset height to match scaled image
+ width = width * ratio; // Reset width to match scaled image
+ }
+
+ // Check if current height is larger than max
+ if (height > maxHeight) {
+ ratio = maxHeight / height; // get ratio for scaling image
+
+ retval.height = maxHeight;
+ retval.width = width * ratio;
+ width = width * ratio; // Reset width to match scaled image
+ }
+
+ return retval;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#getThumbnailFromPath
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns the path to the thumbnail version of a given media library image path
+ *
+ * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
+ */
+ getThumbnailFromPath: function (imagePath) {
+
+ //If the path is not an image we cannot get a thumb
+ if (!this.detectIfImageByExtension(imagePath)) {
+ return null;
+ }
+
+ //get the proxy url for big thumbnails (this ensures one is always generated)
+ var thumbnailUrl = umbRequestHelper.getApiUrl(
+ "imagesApiBaseUrl",
+ "GetBigThumbnail",
+ [{ originalImagePath: imagePath }]);
+
+ //var ext = imagePath.substr(imagePath.lastIndexOf('.'));
+ //return imagePath.substr(0, imagePath.lastIndexOf('.')) + "_big-thumb" + ".jpg";
+
+ return thumbnailUrl;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#detectIfImageByExtension
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns true/false, indicating if the given path has an allowed image extension
+ *
+ * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
+ */
+ detectIfImageByExtension: function (imagePath) {
+
+ if (!imagePath) {
+ return false;
+ }
+
+ var lowered = imagePath.toLowerCase();
+ var ext = lowered.substr(lowered.lastIndexOf(".") + 1);
+ return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#formatFileTypes
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns a string with correctly formated file types for ng-file-upload
+ *
+ * @param {string} file types, ex: jpg,png,tiff
+ */
+ formatFileTypes: function(fileTypes) {
+
+ var fileTypesArray = fileTypes.split(',');
+ var newFileTypesArray = [];
+
+ for (var i = 0; i < fileTypesArray.length; i++) {
+ var fileType = fileTypesArray[i];
+
+ if (fileType.indexOf(".") !== 0) {
+ fileType = ".".concat(fileType);
+ }
+
+ newFileTypesArray.push(fileType);
+ }
+
+ return newFileTypesArray.join(",");
+
+ }
+
+ };
+}angular.module('umbraco.services').factory('mediaHelper', mediaHelper);
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.umbracoMenuActions
+ *
+ * @requires q
+ * @requires treeService
+ *
+ * @description
+ * Defines the methods that are called when menu items declare only an action to execute
+ */
+function umbracoMenuActions($q, treeService, $location, navigationService, appState) {
+
+ return {
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.umbracoMenuActions#RefreshNode
+ * @methodOf umbraco.services.umbracoMenuActions
+ * @function
+ *
+ * @description
+ * Clears all node children and then gets it's up-to-date children from the server and re-assigns them
+ * @param {object} args An arguments object
+ * @param {object} args.entity The basic entity being acted upon
+ * @param {object} args.treeAlias The tree alias associated with this entity
+ * @param {object} args.section The current section
+ */
+ "RefreshNode": function (args) {
+
+ ////just in case clear any tree cache for this node/section
+ //treeService.clearCache({
+ // cacheKey: "__" + args.section, //each item in the tree cache is cached by the section name
+ // childrenOf: args.entity.parentId //clear the children of the parent
+ //});
+
+ //since we're dealing with an entity, we need to attempt to find it's tree node, in the main tree
+ // this action is purely a UI thing so if for whatever reason there is no loaded tree node in the UI
+ // we can safely ignore this process.
+
+ //to find a visible tree node, we'll go get the currently loaded root node from appState
+ var treeRoot = appState.getTreeState("currentRootNode");
+ if (treeRoot && treeRoot.root) {
+ var treeNode = treeService.getDescendantNode(treeRoot.root, args.entity.id, args.treeAlias);
+ if (treeNode) {
+ treeService.loadNodeChildren({ node: treeNode, section: args.section });
+ }
+ }
+
+
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.umbracoMenuActions#CreateChildEntity
+ * @methodOf umbraco.services.umbracoMenuActions
+ * @function
+ *
+ * @description
+ * This will re-route to a route for creating a new entity as a child of the current node
+ * @param {object} args An arguments object
+ * @param {object} args.entity The basic entity being acted upon
+ * @param {object} args.treeAlias The tree alias associated with this entity
+ * @param {object} args.section The current section
+ */
+ "CreateChildEntity": function (args) {
+
+ navigationService.hideNavigation();
+
+ var route = "/" + args.section + "/" + args.treeAlias + "/edit/" + args.entity.id;
+ //change to new path
+ $location.path(route).search({ create: true });
+
+ }
+ };
+}
+
+angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions);
+/**
+ * @ngdoc service
+ * @name umbraco.services.navigationService
+ *
+ * @requires $rootScope
+ * @requires $routeParams
+ * @requires $log
+ * @requires $location
+ * @requires dialogService
+ * @requires treeService
+ * @requires sectionResource
+ *
+ * @description
+ * Service to handle the main application navigation. Responsible for invoking the tree
+ * Section navigation and search, and maintain their state for the entire application lifetime
+ *
+ */
+function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) {
+
+
+ //used to track the current dialog object
+ var currentDialog = null;
+
+ //the main tree event handler, which gets assigned via the setupTreeEvents method
+ var mainTreeEventHandler = null;
+ //tracks the user profile dialog
+ var userDialog = null;
+
+ function setMode(mode) {
+ switch (mode) {
+ case 'tree':
+ appState.setGlobalState("navMode", "tree");
+ appState.setGlobalState("showNavigation", true);
+ appState.setMenuState("showMenu", false);
+ appState.setMenuState("showMenuDialog", false);
+ appState.setGlobalState("stickyNavigation", false);
+ appState.setGlobalState("showTray", false);
+
+ //$("#search-form input").focus();
+ break;
+ case 'menu':
+ appState.setGlobalState("navMode", "menu");
+ appState.setGlobalState("showNavigation", true);
+ appState.setMenuState("showMenu", true);
+ appState.setMenuState("showMenuDialog", false);
+ appState.setGlobalState("stickyNavigation", true);
+ break;
+ case 'dialog':
+ appState.setGlobalState("navMode", "dialog");
+ appState.setGlobalState("stickyNavigation", true);
+ appState.setGlobalState("showNavigation", true);
+ appState.setMenuState("showMenu", false);
+ appState.setMenuState("showMenuDialog", true);
+ break;
+ case 'search':
+ appState.setGlobalState("navMode", "search");
+ appState.setGlobalState("stickyNavigation", false);
+ appState.setGlobalState("showNavigation", true);
+ appState.setMenuState("showMenu", false);
+ appState.setSectionState("showSearchResults", true);
+ appState.setMenuState("showMenuDialog", false);
+
+ //TODO: This would be much better off in the search field controller listening to appState changes
+ $timeout(function() {
+ $("#search-field").focus();
+ });
+
+ break;
+ default:
+ appState.setGlobalState("navMode", "default");
+ appState.setMenuState("showMenu", false);
+ appState.setMenuState("showMenuDialog", false);
+ appState.setSectionState("showSearchResults", false);
+ appState.setGlobalState("stickyNavigation", false);
+ appState.setGlobalState("showTray", false);
+
+ if (appState.getGlobalState("isTablet") === true) {
+ appState.setGlobalState("showNavigation", false);
+ }
+
+ break;
+ }
+ }
+
+ var service = {
+
+ /** initializes the navigation service */
+ init: function() {
+
+ //keep track of the current section - initially this will always be undefined so
+ // no point in setting it now until it changes.
+ $rootScope.$watch(function () {
+ return $routeParams.section;
+ }, function (newVal, oldVal) {
+ appState.setSectionState("currentSection", newVal);
+ });
+
+
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#load
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Shows the legacy iframe and loads in the content based on the source url
+ * @param {String} source The URL to load into the iframe
+ */
+ loadLegacyIFrame: function (source) {
+ $location.path("/" + appState.getSectionState("currentSection") + "/framed/" + encodeURIComponent(source));
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#changeSection
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Changes the active section to a given section alias
+ * If the navigation is 'sticky' this will load the associated tree
+ * and load the dashboard related to the section
+ * @param {string} sectionAlias The alias of the section
+ */
+ changeSection: function(sectionAlias, force) {
+ setMode("default-opensection");
+
+ if (force && appState.getSectionState("currentSection") === sectionAlias) {
+ appState.setSectionState("currentSection", "");
+ }
+
+ appState.setSectionState("currentSection", sectionAlias);
+ this.showTree(sectionAlias);
+
+ $location.path(sectionAlias);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#showTree
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Displays the tree for a given section alias but turning on the containing dom element
+ * only changes if the section is different from the current one
+ * @param {string} sectionAlias The alias of the section to load
+ * @param {Object} syncArgs Optional object of arguments for syncing the tree for the section being shown
+ */
+ showTree: function (sectionAlias, syncArgs) {
+ if (sectionAlias !== appState.getSectionState("currentSection")) {
+ appState.setSectionState("currentSection", sectionAlias);
+
+ if (syncArgs) {
+ this.syncTree(syncArgs);
+ }
+ }
+ setMode("tree");
+ },
+
+ showTray: function () {
+ appState.setGlobalState("showTray", true);
+ },
+
+ hideTray: function () {
+ appState.setGlobalState("showTray", false);
+ },
+
+ /**
+ Called to assign the main tree event handler - this is called by the navigation controller.
+ TODO: Potentially another dev could call this which would kind of mung the whole app so potentially there's a better way.
+ */
+ setupTreeEvents: function(treeEventHandler) {
+ mainTreeEventHandler = treeEventHandler;
+
+ //when a tree is loaded into a section, we need to put it into appState
+ mainTreeEventHandler.bind("treeLoaded", function(ev, args) {
+ appState.setTreeState("currentRootNode", args.tree);
+ });
+
+ //when a tree node is synced this event will fire, this allows us to set the currentNode
+ mainTreeEventHandler.bind("treeSynced", function (ev, args) {
+
+ if (args.activate === undefined || args.activate === true) {
+ //set the current selected node
+ appState.setTreeState("selectedNode", args.node);
+ //when a node is activated, this is the same as clicking it and we need to set the
+ //current menu item to be this node as well.
+ appState.setMenuState("currentNode", args.node);
+ }
+ });
+
+ //this reacts to the options item in the tree
+ mainTreeEventHandler.bind("treeOptionsClick", function(ev, args) {
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ //Set the current action node (this is not the same as the current selected node!)
+ appState.setMenuState("currentNode", args.node);
+
+ if (args.event && args.event.altKey) {
+ args.skipDefault = true;
+ }
+
+ service.showMenu(ev, args);
+ });
+
+ mainTreeEventHandler.bind("treeNodeAltSelect", function(ev, args) {
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ args.skipDefault = true;
+ service.showMenu(ev, args);
+ });
+
+ //this reacts to tree items themselves being clicked
+ //the tree directive should not contain any handling, simply just bubble events
+ mainTreeEventHandler.bind("treeNodeSelect", function (ev, args) {
+ var n = args.node;
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") {
+ //this is a legacy tree node!
+ var jsPrefix = "javascript:";
+ var js;
+ if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) {
+ js = n.metaData["jsClickCallback"].substr(jsPrefix.length);
+ }
+ else {
+ js = n.metaData["jsClickCallback"];
+ }
+ try {
+ var func = eval(js);
+ //this is normally not necessary since the eval above should execute the method and will return nothing.
+ if (func != null && (typeof func === "function")) {
+ func.call();
+ }
+ }
+ catch(ex) {
+ $log.error("Error evaluating js callback from legacy tree node: " + ex);
+ }
+ }
+ else if (n.routePath) {
+ //add action to the history service
+ historyService.add({ name: n.name, link: n.routePath, icon: n.icon });
+
+ //put this node into the tree state
+ appState.setTreeState("selectedNode", args.node);
+ //when a node is clicked we also need to set the active menu node to this node
+ appState.setMenuState("currentNode", args.node);
+
+ //not legacy, lets just set the route value and clear the query string if there is one.
+ $location.path(n.routePath).search("");
+ }
+ else if (args.element.section) {
+ $location.path(args.element.section).search("");
+ }
+
+ service.hideNavigation();
+ });
+ },
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#syncTree
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Syncs a tree with a given path, returns a promise
+ * The path format is: ["itemId","itemId"], and so on
+ * so to sync to a specific document type node do:
+ *
+ * @param {Object} args arguments passed to the function
+ * @param {String} args.tree the tree alias to sync to
+ * @param {Array} args.path the path to sync the tree to
+ * @param {Boolean} args.forceReload optional, specifies whether to force reload the node data from the server even if it already exists in the tree currently
+ * @param {Boolean} args.activate optional, specifies whether to set the synced node to be the active node, this will default to true if not specified
+ */
+ syncTree: function (args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.path) {
+ throw "args.path cannot be null";
+ }
+ if (!args.tree) {
+ throw "args.tree cannot be null";
+ }
+
+ if (mainTreeEventHandler) {
+ //returns a promise
+ return mainTreeEventHandler.syncTree(args);
+ }
+
+ //couldn't sync
+ return angularHelper.rejectedPromise();
+ },
+
+ /**
+ Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
+ have to set an active tree and then sync, the new API does this in one method by using syncTree
+ */
+ _syncPath: function(path, forceReload) {
+ if (mainTreeEventHandler) {
+ mainTreeEventHandler.syncTree({ path: path, forceReload: forceReload });
+ }
+ },
+
+ //TODO: This should return a promise
+ reloadNode: function(node) {
+ if (mainTreeEventHandler) {
+ mainTreeEventHandler.reloadNode(node);
+ }
+ },
+
+ //TODO: This should return a promise
+ reloadSection: function(sectionAlias) {
+ if (mainTreeEventHandler) {
+ mainTreeEventHandler.clearCache({ section: sectionAlias });
+ mainTreeEventHandler.load(sectionAlias);
+ }
+ },
+
+ /**
+ Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
+ have to set an active tree and then sync, the new API does this in one method by using syncTreePath
+ */
+ _setActiveTreeType: function (treeAlias, loadChildren) {
+ if (mainTreeEventHandler) {
+ mainTreeEventHandler._setActiveTreeType(treeAlias, loadChildren);
+ }
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#hideTree
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Hides the tree by hiding the containing dom element
+ */
+ hideTree: function() {
+
+ if (appState.getGlobalState("isTablet") === true && !appState.getGlobalState("stickyNavigation")) {
+ //reset it to whatever is in the url
+ appState.setSectionState("currentSection", $routeParams.section);
+ setMode("default-hidesectiontree");
+ }
+
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#showMenu
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Hides the tree by hiding the containing dom element.
+ * This always returns a promise!
+ *
+ * @param {Event} event the click event triggering the method, passed from the DOM element
+ */
+ showMenu: function(event, args) {
+
+ var deferred = $q.defer();
+ var self = this;
+
+ treeService.getMenu({ treeNode: args.node })
+ .then(function(data) {
+
+ //check for a default
+ //NOTE: event will be undefined when a call to hideDialog is made so it won't re-load the default again.
+ // but perhaps there's a better way to deal with with an additional parameter in the args ? it works though.
+ if (data.defaultAlias && !args.skipDefault) {
+
+ var found = _.find(data.menuItems, function(item) {
+ return item.alias = data.defaultAlias;
+ });
+
+ if (found) {
+
+ //NOTE: This is assigning the current action node - this is not the same as the currently selected node!
+ appState.setMenuState("currentNode", args.node);
+
+ //ensure the current dialog is cleared before creating another!
+ if (currentDialog) {
+ dialogService.close(currentDialog);
+ }
+
+ var dialog = self.showDialog({
+ node: args.node,
+ action: found,
+ section: appState.getSectionState("currentSection")
+ });
+
+ //return the dialog this is opening.
+ deferred.resolve(dialog);
+ return;
+ }
+ }
+
+ //there is no default or we couldn't find one so just continue showing the menu
+
+ setMode("menu");
+
+ appState.setMenuState("currentNode", args.node);
+ appState.setMenuState("menuActions", data.menuItems);
+ appState.setMenuState("dialogTitle", args.node.name);
+
+ //we're not opening a dialog, return null.
+ deferred.resolve(null);
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#hideMenu
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Hides the menu by hiding the containing dom element
+ */
+ hideMenu: function() {
+ //SD: Would we ever want to access the last action'd node instead of clearing it here?
+ appState.setMenuState("currentNode", null);
+ appState.setMenuState("menuActions", []);
+ setMode("tree");
+ },
+
+ /** Executes a given menu action */
+ executeMenuAction: function (action, node, section) {
+
+ if (!action) {
+ throw "action cannot be null";
+ }
+ if (!node) {
+ throw "node cannot be null";
+ }
+ if (!section) {
+ throw "section cannot be null";
+ }
+
+ if (action.metaData && action.metaData["actionRoute"] && angular.isString(action.metaData["actionRoute"])) {
+ //first check if the menu item simply navigates to a route
+ var parts = action.metaData["actionRoute"].split("?");
+ $location.path(parts[0]).search(parts.length > 1 ? parts[1] : "");
+ this.hideNavigation();
+ return;
+ }
+ else if (action.metaData && action.metaData["jsAction"] && angular.isString(action.metaData["jsAction"])) {
+
+ //we'll try to get the jsAction from the injector
+ var menuAction = action.metaData["jsAction"].split('.');
+ if (menuAction.length !== 2) {
+
+ //if it is not two parts long then this most likely means that it's a legacy action
+ var js = action.metaData["jsAction"].replace("javascript:", "");
+ //there's not really a different way to acheive this except for eval
+ eval(js);
+ }
+ else {
+ var menuActionService = $injector.get(menuAction[0]);
+ if (!menuActionService) {
+ throw "The angular service " + menuAction[0] + " could not be found";
+ }
+
+ var method = menuActionService[menuAction[1]];
+
+ if (!method) {
+ throw "The method " + menuAction[1] + " on the angular service " + menuAction[0] + " could not be found";
+ }
+
+ method.apply(this, [{
+ //map our content object to a basic entity to pass in to the menu handlers,
+ //this is required for consistency since a menu item needs to be decoupled from a tree node since the menu can
+ //exist standalone in the editor for which it can only pass in an entity (not tree node).
+ entity: umbModelMapper.convertToEntityBasic(node),
+ action: action,
+ section: section,
+ treeAlias: treeService.getTreeAlias(node)
+ }]);
+ }
+ }
+ else {
+ service.showDialog({
+ node: node,
+ action: action,
+ section: section
+ });
+ }
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#showUserDialog
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Opens the user dialog, next to the sections navigation
+ * template is located in views/common/dialogs/user.html
+ */
+ showUserDialog: function () {
+ // hide tray and close help dialog
+ if (service.helpDialog) {
+ service.helpDialog.close();
+ }
+ service.hideTray();
+
+ if (service.userDialog) {
+ service.userDialog.close();
+ service.userDialog = undefined;
+ }
+
+ service.userDialog = dialogService.open(
+ {
+ template: "views/common/dialogs/user.html",
+ modalClass: "umb-modal-left",
+ show: true
+ });
+
+ return service.userDialog;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#showUserDialog
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Opens the user dialog, next to the sections navigation
+ * template is located in views/common/dialogs/user.html
+ */
+ showHelpDialog: function () {
+ // hide tray and close user dialog
+ service.hideTray();
+ if (service.userDialog) {
+ service.userDialog.close();
+ }
+
+ if(service.helpDialog){
+ service.helpDialog.close();
+ service.helpDialog = undefined;
+ }
+
+ service.helpDialog = dialogService.open(
+ {
+ template: "views/common/dialogs/help.html",
+ modalClass: "umb-modal-left",
+ show: true
+ });
+
+ return service.helpDialog;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#showDialog
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * Opens a dialog, for a given action on a given tree node
+ * uses the dialogService to inject the selected action dialog
+ * into #dialog div.umb-panel-body
+ * the path to the dialog view is determined by:
+ * "views/" + current tree + "/" + action alias + ".html"
+ * The dialog controller will get passed a scope object that is created here with the properties:
+ * scope.currentNode = the selected tree node
+ * scope.currentAction = the selected menu item
+ * so that the dialog controllers can use these properties
+ *
+ * @param {Object} args arguments passed to the function
+ * @param {Scope} args.scope current scope passed to the dialog
+ * @param {Object} args.action the clicked action containing `name` and `alias`
+ */
+ showDialog: function(args) {
+
+ if (!args) {
+ throw "showDialog is missing the args parameter";
+ }
+ if (!args.action) {
+ throw "The args parameter must have an 'action' property as the clicked menu action object";
+ }
+ if (!args.node) {
+ throw "The args parameter must have a 'node' as the active tree node";
+ }
+
+ //ensure the current dialog is cleared before creating another!
+ if (currentDialog) {
+ dialogService.close(currentDialog);
+ currentDialog = null;
+ }
+
+ setMode("dialog");
+
+ //NOTE: Set up the scope object and assign properties, this is legacy functionality but we have to live with it now.
+ // we should be passing in currentNode and currentAction using 'dialogData' for the dialog, not attaching it to a scope.
+ // This scope instance will be destroyed by the dialog so it cannot be a scope that exists outside of the dialog.
+ // If a scope instance has been passed in, we'll have to create a child scope of it, otherwise a new root scope.
+ var dialogScope = args.scope ? args.scope.$new() : $rootScope.$new();
+ dialogScope.currentNode = args.node;
+ dialogScope.currentAction = args.action;
+
+ //the title might be in the meta data, check there first
+ if (args.action.metaData["dialogTitle"]) {
+ appState.setMenuState("dialogTitle", args.action.metaData["dialogTitle"]);
+ }
+ else {
+ appState.setMenuState("dialogTitle", args.action.name);
+ }
+
+ var templateUrl;
+ var iframe;
+
+ if (args.action.metaData["actionUrl"]) {
+ templateUrl = args.action.metaData["actionUrl"];
+ iframe = true;
+ }
+ else if (args.action.metaData["actionView"]) {
+ templateUrl = args.action.metaData["actionView"];
+ iframe = false;
+ }
+ else {
+
+ //by convention we will look into the /views/{treetype}/{action}.html
+ // for example: /views/content/create.html
+
+ //we will also check for a 'packageName' for the current tree, if it exists then the convention will be:
+ // for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html
+
+ var treeAlias = treeService.getTreeAlias(args.node);
+ var packageTreeFolder = treeService.getTreePackageFolder(treeAlias);
+
+ if (!treeAlias) {
+ throw "Could not get tree alias for node " + args.node.id;
+ }
+
+ if (packageTreeFolder) {
+ templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath +
+ "/" + packageTreeFolder +
+ "/backoffice/" + treeAlias + "/" + args.action.alias + ".html";
+ }
+ else {
+ templateUrl = "views/" + treeAlias + "/" + args.action.alias + ".html";
+ }
+
+ iframe = false;
+ }
+
+ //TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with
+ // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog
+ // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window,
+ // though would be v-easy, just not sure we want to ever support that?
+
+ var dialog = dialogService.open(
+ {
+ container: $("#dialog div.umb-modalcolumn-body"),
+ //The ONLY reason we're passing in scope to the dialogService (which is legacy functionality) is
+ // for backwards compatibility since many dialogs require $scope.currentNode or $scope.currentAction
+ // to exist
+ scope: dialogScope,
+ inline: true,
+ show: true,
+ iframe: iframe,
+ modalClass: "umb-dialog",
+ template: templateUrl,
+
+ //These will show up on the dialog controller's $scope under dialogOptions
+ currentNode: args.node,
+ currentAction: args.action,
+ });
+
+ //save the currently assigned dialog so it can be removed before a new one is created
+ currentDialog = dialog;
+ return dialog;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#hideDialog
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * hides the currently open dialog
+ */
+ hideDialog: function (showMenu) {
+
+ setMode("default");
+
+ if(showMenu){
+ this.showMenu(undefined, { skipDefault: true, node: appState.getMenuState("currentNode") });
+ }
+ },
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#showSearch
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * shows the search pane
+ */
+ showSearch: function() {
+ setMode("search");
+ },
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#hideSearch
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * hides the search pane
+ */
+ hideSearch: function() {
+ setMode("default-hidesearch");
+ },
+ /**
+ * @ngdoc method
+ * @name umbraco.services.navigationService#hideNavigation
+ * @methodOf umbraco.services.navigationService
+ *
+ * @description
+ * hides any open navigation panes and resets the tree, actions and the currently selected node
+ */
+ hideNavigation: function() {
+ appState.setMenuState("menuActions", []);
+ setMode("default");
+ }
+ };
+
+ return service;
+}
+
+angular.module('umbraco.services').factory('navigationService', navigationService);
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.notificationsService
+ *
+ * @requires $rootScope
+ * @requires $timeout
+ * @requires angularHelper
+ *
+ * @description
+ * Application-wide service for handling notifications, the umbraco application
+ * maintains a single collection of notications, which the UI watches for changes.
+ * By default when a notication is added, it is automaticly removed 7 seconds after
+ * This can be changed on add()
+ *
+ * ##usage
+ * To use, simply inject the notificationsService into any controller that needs it, and make
+ * sure the umbraco.services module is accesible - which it should be by default.
+ *
+ *
+ */
+angular.module('umbraco.services')
+.factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper) {
+
+ function configureMemberResult(member) {
+ member.menuUrl = umbRequestHelper.getApiUrl("memberTreeBaseUrl", "GetMenu", [{ id: member.id }, { application: 'member' }]);
+ member.editorPath = "member/member/edit/" + (member.key ? member.key : member.id);
+ angular.extend(member.metaData, { treeAlias: "member" });
+ member.subTitle = member.metaData.Email;
+ }
+
+ function configureMediaResult(media)
+ {
+ media.menuUrl = umbRequestHelper.getApiUrl("mediaTreeBaseUrl", "GetMenu", [{ id: media.id }, { application: 'media' }]);
+ media.editorPath = "media/media/edit/" + media.id;
+ angular.extend(media.metaData, { treeAlias: "media" });
+ }
+
+ function configureContentResult(content) {
+ content.menuUrl = umbRequestHelper.getApiUrl("contentTreeBaseUrl", "GetMenu", [{ id: content.id }, { application: 'content' }]);
+ content.editorPath = "content/content/edit/" + content.id;
+ angular.extend(content.metaData, { treeAlias: "content" });
+ content.subTitle = content.metaData.Url;
+ }
+
+ return {
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.searchService#searchMembers
+ * @methodOf umbraco.services.searchService
+ *
+ * @description
+ * Searches the default member search index
+ * @param {Object} args argument object
+ * @param {String} args.term seach term
+ * @returns {Promise} returns promise containing all matching members
+ */
+ searchMembers: function(args) {
+
+ if (!args.term) {
+ throw "args.term is required";
+ }
+
+ return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) {
+ _.each(data, function(item) {
+ configureMemberResult(item);
+ });
+ return data;
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.searchService#searchContent
+ * @methodOf umbraco.services.searchService
+ *
+ * @description
+ * Searches the default internal content search index
+ * @param {Object} args argument object
+ * @param {String} args.term seach term
+ * @returns {Promise} returns promise containing all matching content items
+ */
+ searchContent: function(args) {
+
+ if (!args.term) {
+ throw "args.term is required";
+ }
+
+ return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) {
+ _.each(data, function (item) {
+ configureContentResult(item);
+ });
+ return data;
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.searchService#searchMedia
+ * @methodOf umbraco.services.searchService
+ *
+ * @description
+ * Searches the default media search index
+ * @param {Object} args argument object
+ * @param {String} args.term seach term
+ * @returns {Promise} returns promise containing all matching media items
+ */
+ searchMedia: function(args) {
+
+ if (!args.term) {
+ throw "args.term is required";
+ }
+
+ return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) {
+ _.each(data, function (item) {
+ configureMediaResult(item);
+ });
+ return data;
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.searchService#searchAll
+ * @methodOf umbraco.services.searchService
+ *
+ * @description
+ * Searches all available indexes and returns all results in one collection
+ * @param {Object} args argument object
+ * @param {String} args.term seach term
+ * @returns {Promise} returns promise containing all matching items
+ */
+ searchAll: function (args) {
+
+ if (!args.term) {
+ throw "args.term is required";
+ }
+
+ return entityResource.searchAll(args.term, args.canceler).then(function (data) {
+
+ _.each(data, function(resultByType) {
+ switch(resultByType.type) {
+ case "Document":
+ _.each(resultByType.results, function (item) {
+ configureContentResult(item);
+ });
+ break;
+ case "Media":
+ _.each(resultByType.results, function (item) {
+ configureMediaResult(item);
+ });
+ break;
+ case "Member":
+ _.each(resultByType.results, function (item) {
+ configureMemberResult(item);
+ });
+ break;
+ }
+ });
+
+ return data;
+ });
+
+ },
+
+ //TODO: This doesn't do anything!
+ setCurrent: function(sectionAlias) {
+
+ var currentSection = sectionAlias;
+ }
+ };
+});
+/**
+ * @ngdoc service
+ * @name umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Used to handle server side validation and wires up the UI with the messages. There are 2 types of validation messages, one
+ * is for user defined properties (called Properties) and the other is for field properties which are attached to the native
+ * model objects (not user defined). The methods below are named according to these rules: Properties vs Fields.
+ */
+function serverValidationManager($timeout) {
+
+ var callbacks = [];
+
+ /** calls the callback specified with the errors specified, used internally */
+ function executeCallback(self, errorsForCallback, callback) {
+
+ callback.apply(self, [
+ false, //pass in a value indicating it is invalid
+ errorsForCallback, //pass in the errors for this item
+ self.items]); //pass in all errors in total
+ }
+
+ function getFieldErrors(self, fieldName) {
+ if (!angular.isString(fieldName)) {
+ throw "fieldName must be a string";
+ }
+
+ //find errors for this field name
+ return _.filter(self.items, function (item) {
+ return (item.propertyAlias === null && item.fieldName === fieldName);
+ });
+ }
+
+ function getPropertyErrors(self, propertyAlias, fieldName) {
+ if (!angular.isString(propertyAlias)) {
+ throw "propertyAlias must be a string";
+ }
+ if (fieldName && !angular.isString(fieldName)) {
+ throw "fieldName must be a string";
+ }
+
+ //find all errors for this property
+ return _.filter(self.items, function (item) {
+ return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
+ });
+ }
+
+ return {
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.serverValidationManager#subscribe
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * This method needs to be called once all field and property errors are wired up.
+ *
+ * In some scenarios where the error collection needs to be persisted over a route change
+ * (i.e. when a content item (or any item) is created and the route redirects to the editor)
+ * the controller should call this method once the data is bound to the scope
+ * so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation
+ * colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item.
+ */
+ executeAndClearAllSubscriptions: function() {
+
+ var self = this;
+
+ $timeout(function () {
+
+ for (var cb in callbacks) {
+ if (callbacks[cb].propertyAlias === null) {
+ //its a field error callback
+ var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName);
+ if (fieldErrors.length > 0) {
+ executeCallback(self, fieldErrors, callbacks[cb].callback);
+ }
+ }
+ else {
+ //its a property error
+ var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].fieldName);
+ if (propErrors.length > 0) {
+ executeCallback(self, propErrors, callbacks[cb].callback);
+ }
+ }
+ }
+ //now that they are all executed, we're gonna clear all of the errors we have
+ self.clear();
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.serverValidationManager#subscribe
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Adds a callback method that is executed whenever validation changes for the field name + property specified.
+ * This is generally used for server side validation in order to match up a server side validation error with
+ * a particular field, otherwise we can only pinpoint that there is an error for a content property, not the
+ * property's specific field. This is used with the val-server directive in which the directive specifies the
+ * field alias to listen for.
+ * If propertyAlias is null, then this subscription is for a field property (not a user defined property).
+ */
+ subscribe: function (propertyAlias, fieldName, callback) {
+ if (!callback) {
+ return;
+ }
+
+ if (propertyAlias === null) {
+ //don't add it if it already exists
+ var exists1 = _.find(callbacks, function (item) {
+ return item.propertyAlias === null && item.fieldName === fieldName;
+ });
+ if (!exists1) {
+ callbacks.push({ propertyAlias: null, fieldName: fieldName, callback: callback });
+ }
+ }
+ else if (propertyAlias !== undefined) {
+ //don't add it if it already exists
+ var exists2 = _.find(callbacks, function (item) {
+ return item.propertyAlias === propertyAlias && item.fieldName === fieldName;
+ });
+ if (!exists2) {
+ callbacks.push({ propertyAlias: propertyAlias, fieldName: fieldName, callback: callback });
+ }
+ }
+ },
+
+ unsubscribe: function (propertyAlias, fieldName) {
+
+ if (propertyAlias === null) {
+
+ //remove all callbacks for the content field
+ callbacks = _.reject(callbacks, function (item) {
+ return item.propertyAlias === null && item.fieldName === fieldName;
+ });
+
+ }
+ else if (propertyAlias !== undefined) {
+
+ //remove all callbacks for the content property
+ callbacks = _.reject(callbacks, function (item) {
+ return item.propertyAlias === propertyAlias &&
+ (item.fieldName === fieldName ||
+ ((item.fieldName === undefined || item.fieldName === "") && (fieldName === undefined || fieldName === "")));
+ });
+ }
+
+
+ },
+
+
+ /**
+ * @ngdoc function
+ * @name getPropertyCallbacks
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo.
+ * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an
+ * explicit field name set.
+ */
+ getPropertyCallbacks: function (propertyAlias, fieldName) {
+ var found = _.filter(callbacks, function (item) {
+ //returns any callback that have been registered directly against the field and for only the property
+ return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === "")));
+ });
+ return found;
+ },
+
+ /**
+ * @ngdoc function
+ * @name getFieldCallbacks
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Gets all callbacks that has been registered using the subscribe method for the field.
+ */
+ getFieldCallbacks: function (fieldName) {
+ var found = _.filter(callbacks, function (item) {
+ //returns any callback that have been registered directly against the field
+ return (item.propertyAlias === null && item.fieldName === fieldName);
+ });
+ return found;
+ },
+
+ /**
+ * @ngdoc function
+ * @name addFieldError
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Adds an error message for a native content item field (not a user defined property, for Example, 'Name')
+ */
+ addFieldError: function(fieldName, errorMsg) {
+ if (!fieldName) {
+ return;
+ }
+
+ //only add the item if it doesn't exist
+ if (!this.hasFieldError(fieldName)) {
+ this.items.push({
+ propertyAlias: null,
+ fieldName: fieldName,
+ errorMsg: errorMsg
+ });
+ }
+
+ //find all errors for this item
+ var errorsForCallback = getFieldErrors(this, fieldName);
+ //we should now call all of the call backs registered for this error
+ var cbs = this.getFieldCallbacks(fieldName);
+ //call each callback for this error
+ for (var cb in cbs) {
+ executeCallback(this, errorsForCallback, cbs[cb].callback);
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name addPropertyError
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Adds an error message for the content property
+ */
+ addPropertyError: function (propertyAlias, fieldName, errorMsg) {
+ if (!propertyAlias) {
+ return;
+ }
+
+ //only add the item if it doesn't exist
+ if (!this.hasPropertyError(propertyAlias, fieldName)) {
+ this.items.push({
+ propertyAlias: propertyAlias,
+ fieldName: fieldName,
+ errorMsg: errorMsg
+ });
+ }
+
+ //find all errors for this item
+ var errorsForCallback = getPropertyErrors(this, propertyAlias, fieldName);
+ //we should now call all of the call backs registered for this error
+ var cbs = this.getPropertyCallbacks(propertyAlias, fieldName);
+ //call each callback for this error
+ for (var cb in cbs) {
+ executeCallback(this, errorsForCallback, cbs[cb].callback);
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name removePropertyError
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Removes an error message for the content property
+ */
+ removePropertyError: function (propertyAlias, fieldName) {
+
+ if (!propertyAlias) {
+ return;
+ }
+ //remove the item
+ this.items = _.reject(this.items, function (item) {
+ return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
+ });
+ },
+
+ /**
+ * @ngdoc function
+ * @name reset
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form
+ */
+ reset: function () {
+ this.clear();
+ for (var cb in callbacks) {
+ callbacks[cb].callback.apply(this, [
+ true, //pass in a value indicating it is VALID
+ [], //pass in empty collection
+ []]); //pass in empty collection
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name clear
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Clears all errors
+ */
+ clear: function() {
+ this.items = [];
+ },
+
+ /**
+ * @ngdoc function
+ * @name getPropertyError
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Gets the error message for the content property
+ */
+ getPropertyError: function (propertyAlias, fieldName) {
+ var err = _.find(this.items, function (item) {
+ //return true if the property alias matches and if an empty field name is specified or the field name matches
+ return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
+ });
+ return err;
+ },
+
+ /**
+ * @ngdoc function
+ * @name getFieldError
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Gets the error message for a content field
+ */
+ getFieldError: function (fieldName) {
+ var err = _.find(this.items, function (item) {
+ //return true if the property alias matches and if an empty field name is specified or the field name matches
+ return (item.propertyAlias === null && item.fieldName === fieldName);
+ });
+ return err;
+ },
+
+ /**
+ * @ngdoc function
+ * @name hasPropertyError
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Checks if the content property + field name combo has an error
+ */
+ hasPropertyError: function (propertyAlias, fieldName) {
+ var err = _.find(this.items, function (item) {
+ //return true if the property alias matches and if an empty field name is specified or the field name matches
+ return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
+ });
+ return err ? true : false;
+ },
+
+ /**
+ * @ngdoc function
+ * @name hasFieldError
+ * @methodOf umbraco.services.serverValidationManager
+ * @function
+ *
+ * @description
+ * Checks if a content field has an error
+ */
+ hasFieldError: function (fieldName) {
+ var err = _.find(this.items, function (item) {
+ //return true if the property alias matches and if an empty field name is specified or the field name matches
+ return (item.propertyAlias === null && item.fieldName === fieldName);
+ });
+ return err ? true : false;
+ },
+
+ /** The array of error messages */
+ items: []
+ };
+}
+
+angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager);
+/**
+ * @ngdoc service
+ * @name umbraco.services.tinyMceService
+ *
+ *
+ * @description
+ * A service containing all logic for all of the Umbraco TinyMCE plugins
+ */
+function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService) {
+ return {
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.tinyMceService#configuration
+ * @methodOf umbraco.services.tinyMceService
+ *
+ * @description
+ * Returns a collection of plugins available to the tinyMCE editor
+ *
+ */
+ configuration: function () {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "rteApiBaseUrl",
+ "GetConfiguration"), { cache: true }),
+ 'Failed to retrieve tinymce configuration');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.tinyMceService#defaultPrevalues
+ * @methodOf umbraco.services.tinyMceService
+ *
+ * @description
+ * Returns a default configration to fallback on in case none is provided
+ *
+ */
+ defaultPrevalues: function () {
+ var cfg = {};
+ cfg.toolbar = ["code", "bold", "italic", "styleselect","alignleft", "aligncenter", "alignright", "bullist","numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"];
+ cfg.stylesheets = [];
+ cfg.dimensions = { height: 500 };
+ cfg.maxImageSize = 500;
+ return cfg;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.tinyMceService#createInsertEmbeddedMedia
+ * @methodOf umbraco.services.tinyMceService
+ *
+ * @description
+ * Creates the umbrco insert embedded media tinymce plugin
+ *
+ * @param {Object} editor the TinyMCE editor instance
+ * @param {Object} $scope the current controller scope
+ */
+ createInsertEmbeddedMedia: function (editor, scope, callback) {
+ editor.addButton('umbembeddialog', {
+ icon: 'custom icon-tv',
+ tooltip: 'Embed',
+ onclick: function () {
+ if (callback) {
+ callback();
+ }
+ }
+ });
+ },
+
+ insertEmbeddedMediaInEditor: function(editor, preview) {
+ editor.insertContent(preview);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.tinyMceService#createMediaPicker
+ * @methodOf umbraco.services.tinyMceService
+ *
+ * @description
+ * Creates the umbrco insert media tinymce plugin
+ *
+ * @param {Object} editor the TinyMCE editor instance
+ * @param {Object} $scope the current controller scope
+ */
+ createMediaPicker: function (editor, scope, callback) {
+ editor.addButton('umbmediapicker', {
+ icon: 'custom icon-picture',
+ tooltip: 'Media Picker',
+ onclick: function () {
+
+ var selectedElm = editor.selection.getNode(),
+ currentTarget;
+
+
+ if(selectedElm.nodeName === 'IMG'){
+ var img = $(selectedElm);
+ currentTarget = {
+ altText: img.attr("alt"),
+ url: img.attr("src"),
+ id: img.attr("rel")
+ };
+ }
+
+ userService.getCurrentUser().then(function(userData) {
+ if(callback) {
+ callback(currentTarget, userData);
+ }
+ });
+
+ }
+ });
+ },
+
+ insertMediaInEditor: function(editor, img) {
+ if(img) {
+
+ var data = {
+ alt: img.altText || "",
+ src: (img.url) ? img.url : "nothing.jpg",
+ rel: img.id,
+ 'data-id': img.id,
+ id: '__mcenew'
+ };
+
+ editor.insertContent(editor.dom.createHTML('img', data));
+
+ $timeout(function () {
+ var imgElm = editor.dom.get('__mcenew');
+ var size = editor.dom.getSize(imgElm);
+
+ if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) {
+ var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h);
+
+ var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;";
+ editor.dom.setAttrib(imgElm, 'style', s);
+ editor.dom.setAttrib(imgElm, 'id', null);
+
+ if (img.url) {
+ var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height;
+ editor.dom.setAttrib(imgElm, 'data-mce-src', src);
+ }
+ }
+ }, 500);
+ }
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.tinyMceService#createUmbracoMacro
+ * @methodOf umbraco.services.tinyMceService
+ *
+ * @description
+ * Creates the insert umbrco macro tinymce plugin
+ *
+ * @param {Object} editor the TinyMCE editor instance
+ * @param {Object} $scope the current controller scope
+ */
+ createInsertMacro: function (editor, $scope, callback) {
+
+ var createInsertMacroScope = this;
+
+ /** Adds custom rules for the macro plugin and custom serialization */
+ editor.on('preInit', function (args) {
+ //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out
+ editor.serializer.addRules('div');
+
+ /** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */
+ editor.serializer.addNodeFilter('div', function (nodes, name) {
+ for (var i = 0; i < nodes.length; i++) {
+ if (nodes[i].attr("class") === "umb-macro-holder" && nodes[i].parent && nodes[i].parent.name.toUpperCase() === "P") {
+ nodes[i].parent.unwrap();
+ }
+ }
+ });
+
+ });
+
+ /**
+ * Because the macro gets wrapped in a P tag because of the way 'enter' works, this
+ * method will return the macro element if not wrapped in a p, or the p if the macro
+ * element is the only one inside of it even if we are deep inside an element inside the macro
+ */
+ function getRealMacroElem(element) {
+ var e = $(element).closest(".umb-macro-holder");
+ if (e.length > 0) {
+ if (e.get(0).parentNode.nodeName === "P") {
+ //now check if we're the only element
+ if (element.parentNode.childNodes.length === 1) {
+ return e.get(0).parentNode;
+ }
+ }
+ return e.get(0);
+ }
+ return null;
+ }
+
+ /** Adds the button instance */
+ editor.addButton('umbmacro', {
+ icon: 'custom icon-settings-alt',
+ tooltip: 'Insert macro',
+ onPostRender: function () {
+
+ var ctrl = this;
+ var isOnMacroElement = false;
+
+ /**
+ if the selection comes from a different element that is not the macro's
+ we need to check if the selection includes part of the macro, if so we'll force the selection
+ to clear to the next element since if people can select part of the macro markup they can then modify it.
+ */
+ function handleSelectionChange() {
+
+ if (!editor.selection.isCollapsed()) {
+ var endSelection = tinymce.activeEditor.selection.getEnd();
+ var startSelection = tinymce.activeEditor.selection.getStart();
+ //don't proceed if it's an entire element selected
+ if (endSelection !== startSelection) {
+
+ //if the end selection is a macro then move the cursor
+ //NOTE: we don't have to handle when the selection comes from a previous parent because
+ // that is automatically taken care of with the normal onNodeChanged logic since the
+ // evt.element will be the macro once it becomes part of the selection.
+ var $testForMacro = $(endSelection).closest(".umb-macro-holder");
+ if ($testForMacro.length > 0) {
+
+ //it came from before so move after, if there is no after then select ourselves
+ var next = $testForMacro.next();
+ if (next.length > 0) {
+ editor.selection.setCursorLocation($testForMacro.next().get(0));
+ }
+ else {
+ selectMacroElement($testForMacro.get(0));
+ }
+
+ }
+ }
+ }
+ }
+
+ /** helper method to select the macro element */
+ function selectMacroElement(macroElement) {
+
+ // move selection to top element to ensure we can't edit this
+ editor.selection.select(macroElement);
+
+ // check if the current selection *is* the element (ie bug)
+ var currentSelection = editor.selection.getStart();
+ if (tinymce.isIE) {
+ if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) {
+ while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) {
+ currentSelection = currentSelection.parentNode;
+ }
+ editor.selection.select(currentSelection);
+ }
+ }
+ }
+
+ /**
+ * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag.
+ * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves
+ * from the event listener before changing selection, however, it seems that putting a break point in this method
+ * will always cause an 'infinite' loop as the caret keeps changing.
+ */
+ function onNodeChanged(evt) {
+
+ //set our macro button active when on a node of class umb-macro-holder
+ var $macroElement = $(evt.element).closest(".umb-macro-holder");
+
+ handleSelectionChange();
+
+ //set the button active
+ ctrl.active($macroElement.length !== 0);
+
+ if ($macroElement.length > 0) {
+ var macroElement = $macroElement.get(0);
+
+ //remove the event listener before re-selecting
+ editor.off('NodeChange', onNodeChanged);
+
+ selectMacroElement(macroElement);
+
+ //set the flag
+ isOnMacroElement = true;
+
+ //re-add the event listener
+ editor.on('NodeChange', onNodeChanged);
+ }
+ else {
+ isOnMacroElement = false;
+ }
+
+ }
+
+ /** when the contents load we need to find any macros declared and load in their content */
+ editor.on("LoadContent", function (o) {
+
+ //get all macro divs and load their content
+ $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function() {
+ createInsertMacroScope.loadMacroContent($(this), null, $scope);
+ });
+
+ });
+
+ /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */
+ editor.on('BeforeExecCommand', function (o) {
+ if (isOnMacroElement) {
+ if (o.preventDefault) {
+ o.preventDefault();
+ }
+ if (o.stopImmediatePropagation) {
+ o.stopImmediatePropagation();
+ }
+ return;
+ }
+ });
+
+ /** This double checks and ensures you can't paste content into the rendered macro */
+ editor.on("Paste", function (o) {
+ if (isOnMacroElement) {
+ if (o.preventDefault) {
+ o.preventDefault();
+ }
+ if (o.stopImmediatePropagation) {
+ o.stopImmediatePropagation();
+ }
+ return;
+ }
+ });
+
+ //set onNodeChanged event listener
+ editor.on('NodeChange', onNodeChanged);
+
+ /**
+ * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so
+ * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request
+ * so the macro cannot be edited.
+ */
+ editor.on('KeyDown', function (e) {
+ if (isOnMacroElement) {
+ var macroElement = editor.selection.getNode();
+
+ //get the 'real' element (either p or the real one)
+ macroElement = getRealMacroElem(macroElement);
+
+ //prevent editing
+ e.preventDefault();
+ e.stopPropagation();
+
+ var moveSibling = function (element, isNext) {
+ var $e = $(element);
+ var $sibling = isNext ? $e.next() : $e.prev();
+ if ($sibling.length > 0) {
+ editor.selection.select($sibling.get(0));
+ editor.selection.collapse(true);
+ }
+ else {
+ //if we're moving previous and there is no sibling, then lets recurse and just select the next one
+ if (!isNext) {
+ moveSibling(element, true);
+ return;
+ }
+
+ //if there is no sibling we'll generate a new p at the end and select it
+ editor.setContent(editor.getContent() + "
");
+ editor.selection.select($(editor.dom.getRoot()).children().last().get(0));
+ editor.selection.collapse(true);
+
+ }
+ };
+
+ //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left)
+ //supported keys to remove the macro (8-backspace, 46-delete)
+ //TODO: Should we make the enter key insert a line break before or leave it as moving to the next element?
+ if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) {
+ //move to next element
+ moveSibling(macroElement, true);
+ }
+ else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) {
+ //move to prev element
+ moveSibling(macroElement, false);
+ }
+ else if ($.inArray(e.keyCode, [8, 46]) !== -1) {
+ //delete macro element
+
+ //move first, then delete
+ moveSibling(macroElement, false);
+ editor.dom.remove(macroElement);
+ }
+ return ;
+ }
+ });
+
+ },
+
+ /** The insert macro button click event handler */
+ onclick: function () {
+
+ var dialogData = {
+ //flag for use in rte so we only show macros flagged for the editor
+ richTextEditor: true
+ };
+
+ //when we click we could have a macro already selected and in that case we'll want to edit the current parameters
+ //so we'll need to extract them and submit them to the dialog.
+ var macroElement = editor.selection.getNode();
+ macroElement = getRealMacroElem(macroElement);
+ if (macroElement) {
+ //we have a macro selected so we'll need to parse it's alias and parameters
+ var contents = $(macroElement).contents();
+ var comment = _.find(contents, function(item) {
+ return item.nodeType === 8;
+ });
+ if (!comment) {
+ throw "Cannot parse the current macro, the syntax in the editor is invalid";
+ }
+ var syntax = comment.textContent.trim();
+ var parsed = macroService.parseMacroSyntax(syntax);
+ dialogData = {
+ macroData: parsed
+ };
+ }
+
+ if(callback) {
+ callback(dialogData);
+ }
+
+ }
+ });
+ },
+
+ insertMacroInEditor: function(editor, macroObject, $scope) {
+
+ //put the macro syntax in comments, we will parse this out on the server side to be used
+ //for persisting.
+ var macroSyntaxComment = "";
+ //create an id class for this element so we can re-select it after inserting
+ var uniqueId = "umb-macro-" + editor.dom.uniqueId();
+ var macroDiv = editor.dom.create('div',
+ {
+ 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId
+ },
+ macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + '');
+
+ editor.selection.setNode(macroDiv);
+
+ var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId));
+
+ //async load the macro content
+ this.loadMacroContent($macroDiv, macroObject, $scope);
+
+ },
+
+ /** loads in the macro content async from the server */
+ loadMacroContent: function($macroDiv, macroData, $scope) {
+
+ //if we don't have the macroData, then we'll need to parse it from the macro div
+ if (!macroData) {
+ var contents = $macroDiv.contents();
+ var comment = _.find(contents, function (item) {
+ return item.nodeType === 8;
+ });
+ if (!comment) {
+ throw "Cannot parse the current macro, the syntax in the editor is invalid";
+ }
+ var syntax = comment.textContent.trim();
+ var parsed = macroService.parseMacroSyntax(syntax);
+ macroData = parsed;
+ }
+
+ var $ins = $macroDiv.find("ins");
+
+ //show the throbber
+ $macroDiv.addClass("loading");
+
+ var contentId = $routeParams.id;
+
+ //need to wrap in safe apply since this might be occuring outside of angular
+ angularHelper.safeApply($scope, function() {
+ macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary)
+ .then(function (htmlResult) {
+
+ $macroDiv.removeClass("loading");
+ htmlResult = htmlResult.trim();
+ if (htmlResult !== "") {
+ $ins.html(htmlResult);
+ }
+ });
+ });
+
+ },
+
+ createLinkPicker: function(editor, $scope, onClick) {
+
+ function createLinkList(callback) {
+ return function() {
+ var linkList = editor.settings.link_list;
+
+ if (typeof(linkList) === "string") {
+ tinymce.util.XHR.send({
+ url: linkList,
+ success: function(text) {
+ callback(tinymce.util.JSON.parse(text));
+ }
+ });
+ } else {
+ callback(linkList);
+ }
+ };
+ }
+
+ function showDialog(linkList) {
+ var data = {}, selection = editor.selection, dom = editor.dom, selectedElm, anchorElm, initialText;
+ var win, linkListCtrl, relListCtrl, targetListCtrl;
+
+ function linkListChangeHandler(e) {
+ var textCtrl = win.find('#text');
+
+ if (!textCtrl.value() || (e.lastControl && textCtrl.value() === e.lastControl.text())) {
+ textCtrl.value(e.control.text());
+ }
+
+ win.find('#href').value(e.control.value());
+ }
+
+ function buildLinkList() {
+ var linkListItems = [{
+ text: 'None',
+ value: ''
+ }];
+
+ tinymce.each(linkList, function(link) {
+ linkListItems.push({
+ text: link.text || link.title,
+ value: link.value || link.url,
+ menu: link.menu
+ });
+ });
+
+ return linkListItems;
+ }
+
+ function buildRelList(relValue) {
+ var relListItems = [{
+ text: 'None',
+ value: ''
+ }];
+
+ tinymce.each(editor.settings.rel_list, function(rel) {
+ relListItems.push({
+ text: rel.text || rel.title,
+ value: rel.value,
+ selected: relValue === rel.value
+ });
+ });
+
+ return relListItems;
+ }
+
+ function buildTargetList(targetValue) {
+ var targetListItems = [{
+ text: 'None',
+ value: ''
+ }];
+
+ if (!editor.settings.target_list) {
+ targetListItems.push({
+ text: 'New window',
+ value: '_blank'
+ });
+ }
+
+ tinymce.each(editor.settings.target_list, function(target) {
+ targetListItems.push({
+ text: target.text || target.title,
+ value: target.value,
+ selected: targetValue === target.value
+ });
+ });
+
+ return targetListItems;
+ }
+
+ function buildAnchorListControl(url) {
+ var anchorList = [];
+
+ tinymce.each(editor.dom.select('a:not([href])'), function(anchor) {
+ var id = anchor.name || anchor.id;
+
+ if (id) {
+ anchorList.push({
+ text: id,
+ value: '#' + id,
+ selected: url.indexOf('#' + id) !== -1
+ });
+ }
+ });
+
+ if (anchorList.length) {
+ anchorList.unshift({
+ text: 'None',
+ value: ''
+ });
+
+ return {
+ name: 'anchor',
+ type: 'listbox',
+ label: 'Anchors',
+ values: anchorList,
+ onselect: linkListChangeHandler
+ };
+ }
+ }
+
+ function updateText() {
+ if (!initialText && data.text.length === 0) {
+ this.parent().parent().find('#text')[0].value(this.value());
+ }
+ }
+
+ selectedElm = selection.getNode();
+ anchorElm = dom.getParent(selectedElm, 'a[href]');
+
+ data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({format: 'text'});
+ data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : '';
+ data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : '';
+ data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : '';
+
+ if (selectedElm.nodeName === "IMG") {
+ data.text = initialText = " ";
+ }
+
+ if (linkList) {
+ linkListCtrl = {
+ type: 'listbox',
+ label: 'Link list',
+ values: buildLinkList(),
+ onselect: linkListChangeHandler
+ };
+ }
+
+ if (editor.settings.target_list !== false) {
+ targetListCtrl = {
+ name: 'target',
+ type: 'listbox',
+ label: 'Target',
+ values: buildTargetList(data.target)
+ };
+ }
+
+ if (editor.settings.rel_list) {
+ relListCtrl = {
+ name: 'rel',
+ type: 'listbox',
+ label: 'Rel',
+ values: buildRelList(data.rel)
+ };
+ }
+
+ var injector = angular.element(document.getElementById("umbracoMainPageBody")).injector();
+ var dialogService = injector.get("dialogService");
+ var currentTarget = null;
+
+ //if we already have a link selected, we want to pass that data over to the dialog
+ if(anchorElm){
+ var anchor = $(anchorElm);
+ currentTarget = {
+ name: anchor.attr("title"),
+ url: anchor.attr("href"),
+ target: anchor.attr("target")
+ };
+
+ //locallink detection, we do this here, to avoid poluting the dialogservice
+ //so the dialog service can just expect to get a node-like structure
+ if(currentTarget.url.indexOf("localLink:") > 0){
+ currentTarget.id = currentTarget.url.substring(currentTarget.url.indexOf(":")+1,currentTarget.url.length-1);
+ }
+ }
+
+ if(onClick) {
+ onClick(currentTarget, anchorElm);
+ }
+
+ }
+
+ editor.addButton('link', {
+ icon: 'link',
+ tooltip: 'Insert/edit link',
+ shortcut: 'Ctrl+K',
+ onclick: createLinkList(showDialog),
+ stateSelector: 'a[href]'
+ });
+
+ editor.addButton('unlink', {
+ icon: 'unlink',
+ tooltip: 'Remove link',
+ cmd: 'unlink',
+ stateSelector: 'a[href]'
+ });
+
+ editor.addShortcut('Ctrl+K', '', createLinkList(showDialog));
+ this.showDialog = showDialog;
+
+ editor.addMenuItem('link', {
+ icon: 'link',
+ text: 'Insert link',
+ shortcut: 'Ctrl+K',
+ onclick: createLinkList(showDialog),
+ stateSelector: 'a[href]',
+ context: 'insert',
+ prependToContext: true
+ });
+
+ },
+
+ insertLinkInEditor: function(editor, target, anchorElm) {
+
+ var href = target.url;
+
+ function insertLink() {
+ if (anchorElm) {
+ editor.dom.setAttribs(anchorElm, {
+ href: href,
+ title: target.name,
+ target: target.target ? target.target : null,
+ rel: target.rel ? target.rel : null,
+ 'data-id': target.id ? target.id : null
+ });
+
+ editor.selection.select(anchorElm);
+ editor.execCommand('mceEndTyping');
+ } else {
+ editor.execCommand('mceInsertLink', false, {
+ href: href,
+ title: target.name,
+ target: target.target ? target.target : null,
+ rel: target.rel ? target.rel : null,
+ 'data-id': target.id ? target.id : null
+ });
+ }
+ }
+
+ if (!href) {
+ editor.execCommand('unlink');
+ return;
+ }
+
+ //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set
+ if(target.id && (angular.isUndefined(target.isMedia) || !target.isMedia)){
+ href = "/{localLink:" + target.id + "}";
+ insertLink();
+ return;
+ }
+
+ // Is email and not //user@domain.com
+ if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf('mailto:') === -1) {
+ href = 'mailto:' + href;
+ insertLink();
+ return;
+ }
+
+ // Is www. prefixed
+ if (/^\s*www\./i.test(href)) {
+ href = 'http://' + href;
+ insertLink();
+ return;
+ }
+
+ insertLink();
+
+ }
+
+ };
+}
+
+angular.module('umbraco.services').factory('tinyMceService', tinyMceService);
+
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * The tree service factory, used internally by the umbTree and umbTreeItem directives
+ */
+function treeService($q, treeResource, iconHelper, notificationsService, eventsService) {
+
+ //SD: Have looked at putting this in sessionStorage (not localStorage since that means you wouldn't be able to work
+ // in multiple tabs) - however our tree structure is cyclical, meaning a node has a reference to it's parent and it's children
+ // which you cannot serialize to sessionStorage. There's really no benefit of session storage except that you could refresh
+ // a tab and have the trees where they used to be - supposed that is kind of nice but would mean we'd have to store the parent
+ // as a nodeid reference instead of a variable with a getParent() method.
+ var treeCache = {};
+
+ var standardCssClass = 'icon umb-tree-icon sprTree';
+
+ function getCacheKey(args) {
+ //if there is no cache key they return null - it won't be cached.
+ if (!args || !args.cacheKey) {
+ return null;
+ }
+
+ var cacheKey = args.cacheKey;
+ cacheKey += "_" + args.section;
+ return cacheKey;
+ }
+
+ return {
+
+ /** Internal method to return the tree cache */
+ _getTreeCache: function() {
+ return treeCache;
+ },
+
+ /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */
+ _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) {
+ //if no level is set, then we make it 1
+ var childLevel = (level ? level : 1);
+ //set the section if it's not already set
+ if (!parentNode.section) {
+ parentNode.section = section;
+ }
+ //create a method outside of the loop to return the parent - otherwise jshint blows up
+ var funcParent = function() {
+ return parentNode;
+ };
+ for (var i = 0; i < treeNodes.length; i++) {
+
+ treeNodes[i].level = childLevel;
+
+ //create a function to get the parent node, we could assign the parent node but
+ // then we cannot serialize this entity because we have a cyclical reference.
+ // Instead we just make a function to return the parentNode.
+ treeNodes[i].parent = funcParent;
+
+ //set the section for each tree node - this allows us to reference this easily when accessing tree nodes
+ treeNodes[i].section = section;
+
+ //if there is not route path specified, then set it automatically,
+ //if this is a tree root node then we want to route to the section's dashboard
+ if (!treeNodes[i].routePath) {
+
+ if (treeNodes[i].metaData && treeNodes[i].metaData["treeAlias"]) {
+ //this is a root node
+ treeNodes[i].routePath = section;
+ }
+ else {
+ var treeAlias = this.getTreeAlias(treeNodes[i]);
+ treeNodes[i].routePath = section + "/" + treeAlias + "/edit/" + treeNodes[i].id;
+ }
+ }
+
+ //now, format the icon data
+ if (treeNodes[i].iconIsClass === undefined || treeNodes[i].iconIsClass) {
+ var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNodes[i]);
+ treeNodes[i].cssClass = standardCssClass + " " + converted;
+ if (converted.startsWith('.')) {
+ //its legacy so add some width/height
+ treeNodes[i].style = "height:16px;width:16px;";
+ }
+ else {
+ treeNodes[i].style = "";
+ }
+ }
+ else {
+ treeNodes[i].style = "background-image: url('" + treeNodes[i].iconFilePath + "');";
+ //we need an 'icon-' class in there for certain styles to work so if it is image based we'll add this
+ treeNodes[i].cssClass = standardCssClass + " legacy-custom-file";
+ }
+ }
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getTreePackageFolder
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Determines if the current tree is a plugin tree and if so returns the package folder it has declared
+ * so we know where to find it's views, otherwise it will just return undefined.
+ *
+ * @param {String} treeAlias The tree alias to check
+ */
+ getTreePackageFolder: function(treeAlias) {
+ //we determine this based on the server variables
+ if (Umbraco.Sys.ServerVariables.umbracoPlugins &&
+ Umbraco.Sys.ServerVariables.umbracoPlugins.trees &&
+ angular.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) {
+
+ var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) {
+ return item.alias === treeAlias;
+ });
+
+ return found ? found.packageFolder : undefined;
+ }
+ return undefined;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#clearCache
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Clears the tree cache - with optional cacheKey, optional section or optional filter.
+ *
+ * @param {Object} args arguments
+ * @param {String} args.cacheKey optional cachekey - this is used to clear specific trees in dialogs
+ * @param {String} args.section optional section alias - clear tree for a given section
+ * @param {String} args.childrenOf optional parent ID - only clear the cache below a specific node
+ */
+ clearCache: function (args) {
+ //clear all if not specified
+ if (!args) {
+ treeCache = {};
+ }
+ else {
+ //if section and cache key specified just clear that cache
+ if (args.section && args.cacheKey) {
+ var cacheKey = getCacheKey(args);
+ if (cacheKey && treeCache && treeCache[cacheKey] != null) {
+ treeCache = _.omit(treeCache, cacheKey);
+ }
+ }
+ else if (args.childrenOf) {
+ //if childrenOf is supplied a cacheKey must be supplied as well
+ if (!args.cacheKey) {
+ throw "args.cacheKey is required if args.childrenOf is supplied";
+ }
+ //this will clear out all children for the parentId passed in to this parameter, we'll
+ // do this by recursing and specifying a filter
+ var self = this;
+ this.clearCache({
+ cacheKey: args.cacheKey,
+ filter: function(cc) {
+ //get the new parent node from the tree cache
+ var parent = self.getDescendantNode(cc.root, args.childrenOf);
+ if (parent) {
+ //clear it's children and set to not expanded
+ parent.children = null;
+ parent.expanded = false;
+ }
+ //return the cache to be saved
+ return cc;
+ }
+ });
+ }
+ else if (args.filter && angular.isFunction(args.filter)) {
+ //if a filter is supplied a cacheKey must be supplied as well
+ if (!args.cacheKey) {
+ throw "args.cacheKey is required if args.filter is supplied";
+ }
+
+ //if a filter is supplied the function needs to return the data to keep
+ var byKey = treeCache[args.cacheKey];
+ if (byKey) {
+ var result = args.filter(byKey);
+
+ if (result) {
+ //set the result to the filtered data
+ treeCache[args.cacheKey] = result;
+ }
+ else {
+ //remove the cache
+ treeCache = _.omit(treeCache, args.cacheKey);
+ }
+
+ }
+
+ }
+ else if (args.cacheKey) {
+ //if only the cache key is specified, then clear all cache starting with that key
+ var allKeys1 = _.keys(treeCache);
+ var toRemove1 = _.filter(allKeys1, function (k) {
+ return k.startsWith(args.cacheKey + "_");
+ });
+ treeCache = _.omit(treeCache, toRemove1);
+ }
+ else if (args.section) {
+ //if only the section is specified then clear all cache regardless of cache key by that section
+ var allKeys2 = _.keys(treeCache);
+ var toRemove2 = _.filter(allKeys2, function (k) {
+ return k.endsWith("_" + args.section);
+ });
+ treeCache = _.omit(treeCache, toRemove2);
+ }
+ }
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#loadNodeChildren
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Clears all node children, gets it's up-to-date children from the server and re-assigns them and then
+ * returns them in a promise.
+ * @param {object} args An arguments object
+ * @param {object} args.node The tree node
+ * @param {object} args.section The current section
+ */
+ loadNodeChildren: function(args) {
+ if (!args) {
+ throw "No args object defined for loadNodeChildren";
+ }
+ if (!args.node) {
+ throw "No node defined on args object for loadNodeChildren";
+ }
+
+ this.removeChildNodes(args.node);
+ args.node.loading = true;
+
+ return this.getChildren(args)
+ .then(function(data) {
+
+ //set state to done and expand (only if there actually are children!)
+ args.node.loading = false;
+ args.node.children = data;
+ if (args.node.children && args.node.children.length > 0) {
+ args.node.expanded = true;
+ args.node.hasChildren = true;
+ }
+ return data;
+
+ }, function(reason) {
+
+ //in case of error, emit event
+ eventsService.emit("treeService.treeNodeLoadError", {error: reason } );
+
+ //stop show the loading indicator
+ args.node.loading = false;
+
+ //tell notications about the error
+ notificationsService.error(reason);
+
+ return reason;
+ });
+
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#removeNode
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Removes a given node from the tree
+ * @param {object} treeNode the node to remove
+ */
+ removeNode: function(treeNode) {
+ if (!angular.isFunction(treeNode.parent)) {
+ return;
+ }
+
+ if (treeNode.parent() == null) {
+ throw "Cannot remove a node that doesn't have a parent";
+ }
+ //remove the current item from it's siblings
+ treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#removeChildNodes
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Removes all child nodes from a given tree node
+ * @param {object} treeNode the node to remove children from
+ */
+ removeChildNodes : function(treeNode) {
+ treeNode.expanded = false;
+ treeNode.children = [];
+ treeNode.hasChildren = false;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getChildNode
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Gets a child node with a given ID, from a specific treeNode
+ * @param {object} treeNode to retrive child node from
+ * @param {int} id id of child node
+ */
+ getChildNode: function (treeNode, id) {
+ if (!treeNode.children) {
+ return null;
+ }
+ var found = _.find(treeNode.children, function (child) {
+ return String(child.id).toLowerCase() === String(id).toLowerCase();
+ });
+ return found === undefined ? null : found;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getDescendantNode
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Gets a descendant node by id
+ * @param {object} treeNode to retrive descendant node from
+ * @param {int} id id of descendant node
+ * @param {string} treeAlias - optional tree alias, if fetching descendant node from a child of a listview document
+ */
+ getDescendantNode: function(treeNode, id, treeAlias) {
+
+ //validate if it is a section container since we'll need a treeAlias if it is one
+ if (treeNode.isContainer === true && !treeAlias) {
+ throw "Cannot get a descendant node from a section container node without a treeAlias specified";
+ }
+
+ //if it is a section container, we need to find the tree to be searched
+ if (treeNode.isContainer) {
+ var foundRoot = null;
+ for (var c = 0; c < treeNode.children.length; c++) {
+ if (this.getTreeAlias(treeNode.children[c]) === treeAlias) {
+ foundRoot = treeNode.children[c];
+ break;
+ }
+ }
+ if (!foundRoot) {
+ throw "Could not find a tree in the current section with alias " + treeAlias;
+ }
+ treeNode = foundRoot;
+ }
+
+ //check this node
+ if (treeNode.id === id) {
+ return treeNode;
+ }
+
+ //check the first level
+ var found = this.getChildNode(treeNode, id);
+ if (found) {
+ return found;
+ }
+
+ //check each child of this node
+ if (!treeNode.children) {
+ return null;
+ }
+
+ for (var i = 0; i < treeNode.children.length; i++) {
+ if (treeNode.children[i].children && angular.isArray(treeNode.children[i].children) && treeNode.children[i].children.length > 0) {
+ //recurse
+ found = this.getDescendantNode(treeNode.children[i], id);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ //not found
+ return found === undefined ? null : found;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getTreeRoot
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Gets the root node of the current tree type for a given tree node
+ * @param {object} treeNode to retrive tree root node from
+ */
+ getTreeRoot: function (treeNode) {
+ if (!treeNode) {
+ throw "treeNode cannot be null";
+ }
+
+ //all root nodes have metadata key 'treeAlias'
+ var root = null;
+ var current = treeNode;
+ while (root === null && current) {
+
+ if (current.metaData && current.metaData["treeAlias"]) {
+ root = current;
+ }
+ else if (angular.isFunction(current.parent)) {
+ //we can only continue if there is a parent() method which means this
+ // tree node was loaded in as part of a real tree, not just as a single tree
+ // node from the server.
+ current = current.parent();
+ }
+ else {
+ current = null;
+ }
+ }
+ return root;
+ },
+
+ /** Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node */
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getTreeAlias
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node
+ * @param {object} treeNode to retrive tree alias from
+ */
+ getTreeAlias : function(treeNode) {
+ var root = this.getTreeRoot(treeNode);
+ if (root) {
+ return root.metaData["treeAlias"];
+ }
+ return "";
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getTree
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * gets the tree, returns a promise
+ * @param {object} args Arguments
+ * @param {string} args.section Section alias
+ * @param {string} args.cacheKey Optional cachekey
+ */
+ getTree: function (args) {
+
+ var deferred = $q.defer();
+
+ //set defaults
+ if (!args) {
+ args = { section: 'content', cacheKey: null };
+ }
+ else if (!args.section) {
+ args.section = 'content';
+ }
+
+ var cacheKey = getCacheKey(args);
+
+ //return the cache if it exists
+ if (cacheKey && treeCache[cacheKey] !== undefined) {
+ deferred.resolve(treeCache[cacheKey]);
+ return deferred.promise;
+ }
+
+ var self = this;
+ treeResource.loadApplication(args)
+ .then(function(data) {
+ //this will be called once the tree app data has loaded
+ var result = {
+ name: data.name,
+ alias: args.section,
+ root: data
+ };
+ //we need to format/modify some of the node data to be used in our app.
+ self._formatNodeDataForUseInUI(result.root, result.root.children, args.section);
+
+ //cache this result if a cache key is specified - generally a cache key should ONLY
+ // be specified for application trees, dialog trees should not be cached.
+ if (cacheKey) {
+ treeCache[cacheKey] = result;
+ deferred.resolve(treeCache[cacheKey]);
+ }
+
+ //return un-cached
+ deferred.resolve(result);
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getMenu
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Returns available menu actions for a given tree node
+ * @param {object} args Arguments
+ * @param {string} args.treeNode tree node object to retrieve the menu for
+ */
+ getMenu: function (args) {
+
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.treeNode) {
+ throw "args.treeNode cannot be null";
+ }
+
+ return treeResource.loadMenu(args.treeNode)
+ .then(function(data) {
+ //need to convert the icons to new ones
+ for (var i = 0; i < data.length; i++) {
+ data[i].cssclass = iconHelper.convertFromLegacyIcon(data[i].cssclass);
+ }
+ return data;
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getChildren
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Gets the children from the server for a given node
+ * @param {object} args Arguments
+ * @param {object} args.node tree node object to retrieve the children for
+ * @param {string} args.section current section alias
+ */
+ getChildren: function (args) {
+
+ if (!args) {
+ throw "No args object defined for getChildren";
+ }
+ if (!args.node) {
+ throw "No node defined on args object for getChildren";
+ }
+
+ var section = args.section || 'content';
+ var treeItem = args.node;
+
+ var self = this;
+
+ return treeResource.loadNodes({ node: treeItem })
+ .then(function (data) {
+ //now that we have the data, we need to add the level property to each item and the view
+ self._formatNodeDataForUseInUI(treeItem, data, section, treeItem.level + 1);
+ return data;
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#reloadNode
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Re-loads the single node from the server
+ * @param {object} node Tree node to reload
+ */
+ reloadNode: function(node) {
+ if (!node) {
+ throw "node cannot be null";
+ }
+ if (!node.parent()) {
+ throw "cannot reload a single node without a parent";
+ }
+ if (!node.section) {
+ throw "cannot reload a single node without an assigned node.section";
+ }
+
+ var deferred = $q.defer();
+
+ //set the node to loading
+ node.loading = true;
+
+ this.getChildren({ node: node.parent(), section: node.section }).then(function(data) {
+
+ //ok, now that we have the children, find the node we're reloading
+ var found = _.find(data, function(item) {
+ return item.id === node.id;
+ });
+ if (found) {
+ //now we need to find the node in the parent.children collection to replace
+ var index = _.indexOf(node.parent().children, node);
+ //the trick here is to not actually replace the node - this would cause the delete animations
+ //to fire, instead we're just going to replace all the properties of this node.
+
+ //there should always be a method assigned but we'll check anyways
+ if (angular.isFunction(node.parent().children[index].updateNodeData)) {
+ node.parent().children[index].updateNodeData(found);
+ }
+ else {
+ //just update as per normal - this means styles, etc.. won't be applied
+ _.extend(node.parent().children[index], found);
+ }
+
+ //set the node loading
+ node.parent().children[index].loading = false;
+ //return
+ deferred.resolve(node.parent().children[index]);
+ }
+ else {
+ deferred.reject();
+ }
+ }, function() {
+ deferred.reject();
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getPath
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * This will return the current node's path by walking up the tree
+ * @param {object} node Tree node to retrieve path for
+ */
+ getPath: function(node) {
+ if (!node) {
+ throw "node cannot be null";
+ }
+ if (!angular.isFunction(node.parent)) {
+ throw "node.parent is not a function, the path cannot be resolved";
+ }
+ //all root nodes have metadata key 'treeAlias'
+ var reversePath = [];
+ var current = node;
+ while (current != null) {
+ reversePath.push(current.id);
+ if (current.metaData && current.metaData["treeAlias"]) {
+ current = null;
+ }
+ else {
+ current = current.parent();
+ }
+ }
+ return reversePath.reverse();
+ },
+
+ syncTree: function(args) {
+
+ if (!args) {
+ throw "No args object defined for syncTree";
+ }
+ if (!args.node) {
+ throw "No node defined on args object for syncTree";
+ }
+ if (!args.path) {
+ throw "No path defined on args object for syncTree";
+ }
+ if (!angular.isArray(args.path)) {
+ throw "Path must be an array";
+ }
+ if (args.path.length < 1) {
+ //if there is no path, make -1 the path, and that should sync the tree root
+ args.path.push("-1");
+ }
+
+ var deferred = $q.defer();
+
+ //get the rootNode for the current node, we'll sync based on that
+ var root = this.getTreeRoot(args.node);
+ if (!root) {
+ throw "Could not get the root tree node based on the node passed in";
+ }
+
+ //now we want to loop through the ids in the path, first we'll check if the first part
+ //of the path is the root node, otherwise we'll search it's children.
+ var currPathIndex = 0;
+ //if the first id is the root node and there's only one... then consider it synced
+ if (String(args.path[currPathIndex]).toLowerCase() === String(args.node.id).toLowerCase()) {
+ if (args.path.length === 1) {
+ //return the root
+ deferred.resolve(root);
+ return deferred.promise;
+ }
+ else {
+ //move to the next path part and continue
+ currPathIndex = 1;
+ }
+ }
+
+ //now that we have the first id to lookup, we can start the process
+
+ var self = this;
+ var node = args.node;
+
+ var doSync = function () {
+ //check if it exists in the already loaded children
+ var child = self.getChildNode(node, args.path[currPathIndex]);
+ if (child) {
+ if (args.path.length === (currPathIndex + 1)) {
+ //woot! synced the node
+ if (!args.forceReload) {
+ deferred.resolve(child);
+ }
+ else {
+ //even though we've found the node if forceReload is specified
+ //we want to go update this single node from the server
+ self.reloadNode(child).then(function (reloaded) {
+ deferred.resolve(reloaded);
+ }, function () {
+ deferred.reject();
+ });
+ }
+ }
+ else {
+ //now we need to recurse with the updated node/currPathIndex
+ currPathIndex++;
+ node = child;
+ //recurse
+ doSync();
+ }
+ }
+ else {
+ //couldn't find it in the
+ self.loadNodeChildren({ node: node, section: node.section }).then(function () {
+ //ok, got the children, let's find it
+ var found = self.getChildNode(node, args.path[currPathIndex]);
+ if (found) {
+ if (args.path.length === (currPathIndex + 1)) {
+ //woot! synced the node
+ deferred.resolve(found);
+ }
+ else {
+ //now we need to recurse with the updated node/currPathIndex
+ currPathIndex++;
+ node = found;
+ //recurse
+ doSync();
+ }
+ }
+ else {
+ //fail!
+ deferred.reject();
+ }
+ }, function () {
+ //fail!
+ deferred.reject();
+ });
+ }
+ };
+
+ //start
+ doSync();
+
+ return deferred.promise;
+
+ }
+
+ };
+}
+
+angular.module('umbraco.services').factory('treeService', treeService);
+/**
+* @ngdoc service
+* @name umbraco.services.umbRequestHelper
+* @description A helper object used for sending requests to the server
+**/
+function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) {
+ return {
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath
+ * @methodOf umbraco.services.umbRequestHelper
+ * @function
+ *
+ * @description
+ * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path
+ *
+ * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown
+ */
+ convertVirtualToAbsolutePath: function(virtualPath) {
+ if (virtualPath.startsWith("/")) {
+ return virtualPath;
+ }
+ if (!virtualPath.startsWith("~/")) {
+ throw "The path " + virtualPath + " is not a virtual path";
+ }
+ if (!Umbraco.Sys.ServerVariables.application.applicationPath) {
+ throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath";
+ }
+ return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/");
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.umbRequestHelper#dictionaryToQueryString
+ * @methodOf umbraco.services.umbRequestHelper
+ * @function
+ *
+ * @description
+ * This will turn an array of key/value pairs into a query string
+ *
+ * @param {Array} queryStrings An array of key/value pairs
+ */
+ dictionaryToQueryString: function (queryStrings) {
+
+ if (angular.isArray(queryStrings)) {
+ return _.map(queryStrings, function (item) {
+ var key = null;
+ var val = null;
+ for (var k in item) {
+ key = k;
+ val = item[k];
+ break;
+ }
+ if (key === null || val === null) {
+ throw "The object in the array was not formatted as a key/value pair";
+ }
+ return encodeURIComponent(key) + "=" + encodeURIComponent(val);
+ }).join("&");
+ }
+ else if (angular.isObject(queryStrings)) {
+
+ //this allows for a normal object to be passed in (ie. a dictionary)
+ return decodeURIComponent($.param(queryStrings));
+ }
+
+ throw "The queryString parameter is not an array or object of key value pairs";
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.umbRequestHelper#getApiUrl
+ * @methodOf umbraco.services.umbRequestHelper
+ * @function
+ *
+ * @description
+ * This will return the webapi Url for the requested key based on the servervariables collection
+ *
+ * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary
+ * @param {string} actionName The webapi action name
+ * @param {object} queryStrings Can be either a string or an array containing key/value pairs
+ */
+ getApiUrl: function (apiName, actionName, queryStrings) {
+ if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) {
+ throw "No server variables defined!";
+ }
+
+ if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) {
+ throw "No url found for api name " + apiName;
+ }
+
+ return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName +
+ (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings)));
+
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.umbRequestHelper#resourcePromise
+ * @methodOf umbraco.services.umbRequestHelper
+ * @function
+ *
+ * @description
+ * This returns a promise with an underlying http call, it is a helper method to reduce
+ * the amount of duplicate code needed to query http resources and automatically handle any
+ * Http errors. See /docs/source/using-promises-resources.md
+ *
+ * @param {object} opts A mixed object which can either be a string representing the error message to be
+ * returned OR an object containing either:
+ * { success: successCallback, errorMsg: errorMessage }
+ * OR
+ * { success: successCallback, error: errorCallback }
+ * In both of the above, the successCallback must accept these parameters: data, status, headers, config
+ * If using the errorCallback it must accept these parameters: data, status, headers, config
+ * The success callback must return the data which will be resolved by the deferred object.
+ * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status }
+ */
+ resourcePromise: function (httpPromise, opts) {
+ var deferred = $q.defer();
+
+ /** The default success callback used if one is not supplied in the opts */
+ function defaultSuccess(data, status, headers, config) {
+ //when it's successful, just return the data
+ return data;
+ }
+
+ /** The default error callback used if one is not supplied in the opts */
+ function defaultError(data, status, headers, config) {
+ return {
+ //NOTE: the default error message here should never be used based on the above docs!
+ errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'),
+ data: data,
+ status: status
+ };
+ }
+
+ //create the callbacs based on whats been passed in.
+ var callbacks = {
+ success: ((!opts || !opts.success) ? defaultSuccess : opts.success),
+ error: ((!opts || !opts.error) ? defaultError : opts.error)
+ };
+
+ httpPromise.success(function (data, status, headers, config) {
+
+ //invoke the callback
+ var result = callbacks.success.apply(this, [data, status, headers, config]);
+
+ //when it's successful, just return the data
+ deferred.resolve(result);
+
+ }).error(function (data, status, headers, config) {
+
+ //invoke the callback
+ var result = callbacks.error.apply(this, [data, status, headers, config]);
+
+ //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled.
+ if (status >= 500 && status < 600) {
+
+ //show a ysod dialog
+ if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) {
+ eventsService.emit('app.ysod',
+ {
+ errorMsg: 'An error occured',
+ data: data
+ });
+ }
+ else {
+ //show a simple error notification
+ notificationsService.error("Server error", "Contact administrator, see log for full details. " + result.errorMsg + "");
+ }
+
+ }
+
+ //return an error object including the error message for UI
+ deferred.reject({
+ errorMsg: result.errorMsg,
+ data: result.data,
+ status: result.status
+ });
+
+
+ });
+
+ return deferred.promise;
+
+ },
+
+ /** Used for saving media/content specifically */
+ postSaveContent: function (args) {
+
+ if (!args.restApiUrl) {
+ throw "args.restApiUrl is a required argument";
+ }
+ if (!args.content) {
+ throw "args.content is a required argument";
+ }
+ if (!args.action) {
+ throw "args.action is a required argument";
+ }
+ if (!args.files) {
+ throw "args.files is a required argument";
+ }
+ if (!args.dataFormatter) {
+ throw "args.dataFormatter is a required argument";
+ }
+
+
+ var deferred = $q.defer();
+
+ //save the active tab id so we can set it when the data is returned.
+ var activeTab = _.find(args.content.tabs, function (item) {
+ return item.active;
+ });
+ var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab));
+
+ //save the data
+ this.postMultiPartRequest(
+ args.restApiUrl,
+ { key: "contentItem", value: args.dataFormatter(args.content, args.action) },
+ function (data, formData) {
+ //now add all of the assigned files
+ for (var f in args.files) {
+ //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key
+ // so we know which property it belongs to on the server side
+ formData.append("file_" + args.files[f].alias, args.files[f].file);
+ }
+
+ },
+ function (data, status, headers, config) {
+ //success callback
+
+ //reset the tabs and set the active one
+ _.each(data.tabs, function (item) {
+ item.active = false;
+ });
+ data.tabs[activeTabIndex].active = true;
+
+ //the data returned is the up-to-date data so the UI will refresh
+ deferred.resolve(data);
+ },
+ function (data, status, headers, config) {
+ //failure callback
+
+ //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled.
+ if (status >= 500 && status < 600) {
+
+ //This is a bit of a hack to check if the error is due to a file being uploaded that is too large,
+ // we have to just check for the existence of a string value but currently that is the best way to
+ // do this since it's very hacky/difficult to catch this on the server
+ if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) {
+ notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed");
+ }
+ else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) {
+ //show a ysod dialog
+ eventsService.emit('app.ysod',
+ {
+ errorMsg: 'An error occured',
+ data: data
+ });
+ }
+ else {
+ //show a simple error notification
+ notificationsService.error("Server error", "Contact administrator, see log for full details. " + data.ExceptionMessage + "");
+ }
+
+ }
+
+ //return an error object including the error message for UI
+ deferred.reject({
+ errorMsg: 'An error occurred',
+ data: data,
+ status: status
+ });
+
+
+ });
+
+ return deferred.promise;
+ },
+
+ /** Posts a multi-part mime request to the server */
+ postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) {
+
+ //validate input, jsonData can be an array of key/value pairs or just one key/value pair.
+ if (!jsonData) { throw "jsonData cannot be null"; }
+
+ if (angular.isArray(jsonData)) {
+ _.each(jsonData, function (item) {
+ if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; }
+ });
+ }
+ else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; }
+
+
+ $http({
+ method: 'POST',
+ url: url,
+ //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files
+ // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request
+ // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false'
+ // will force the request to automatically populate the headers properly including the boundary parameter.
+ headers: { 'Content-Type': false },
+ transformRequest: function (data) {
+ var formData = new FormData();
+ //add the json data
+ if (angular.isArray(data)) {
+ _.each(data, function (item) {
+ formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value);
+ });
+ }
+ else {
+ formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value);
+ }
+
+ //call the callback
+ if (transformCallback) {
+ transformCallback.apply(this, [data, formData]);
+ }
+
+ return formData;
+ },
+ data: jsonData
+ }).
+ success(function (data, status, headers, config) {
+ if (successCallback) {
+ successCallback.apply(this, [data, status, headers, config]);
+ }
+ }).
+ error(function (data, status, headers, config) {
+ if (failureCallback) {
+ failureCallback.apply(this, [data, status, headers, config]);
+ }
+ });
+ }
+ };
+}
+angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper);
+
+angular.module('umbraco.services')
+ .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) {
+
+ var currentUser = null;
+ var lastUserId = null;
+ var loginDialog = null;
+ //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server
+ // this is used so that we know when to go and get the user's remaining seconds directly.
+ var lastServerTimeoutSet = null;
+
+ function openLoginDialog(isTimedOut) {
+ if (!loginDialog) {
+ loginDialog = dialogService.open({
+
+ //very special flag which means that global events cannot close this dialog
+ manualClose: true,
+
+ template: 'views/common/dialogs/login.html',
+ modalClass: "login-overlay",
+ animation: "slide",
+ show: true,
+ callback: onLoginDialogClose,
+ dialogData: {
+ isTimedOut: isTimedOut
+ }
+ });
+ }
+ }
+
+ function onLoginDialogClose(success) {
+ loginDialog = null;
+
+ if (success) {
+ securityRetryQueue.retryAll(currentUser.name);
+ }
+ else {
+ securityRetryQueue.cancelAll();
+ $location.path('/');
+ }
+ }
+
+ /**
+ This methods will set the current user when it is resolved and
+ will then start the counter to count in-memory how many seconds they have
+ remaining on the auth session
+ */
+ function setCurrentUser(usr) {
+ if (!usr.remainingAuthSeconds) {
+ throw "The user object is invalid, the remainingAuthSeconds is required.";
+ }
+ currentUser = usr;
+ lastServerTimeoutSet = new Date();
+ //start the timer
+ countdownUserTimeout();
+ }
+
+ /**
+ Method to count down the current user's timeout seconds,
+ this will continually count down their current remaining seconds every 5 seconds until
+ there are no more seconds remaining.
+ */
+ function countdownUserTimeout() {
+
+ $timeout(function () {
+
+ if (currentUser) {
+ //countdown by 5 seconds since that is how long our timer is for.
+ currentUser.remainingAuthSeconds -= 5;
+
+ //if there are more than 30 remaining seconds, recurse!
+ if (currentUser.remainingAuthSeconds > 30) {
+
+ //we need to check when the last time the timeout was set from the server, if
+ // it has been more than 30 seconds then we'll manually go and retrieve it from the
+ // server - this helps to keep our local countdown in check with the true timeout.
+ if (lastServerTimeoutSet != null) {
+ var now = new Date();
+ var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000;
+
+ if (seconds > 30) {
+
+ //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
+ // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
+ lastServerTimeoutSet = null;
+
+ //now go get it from the server
+ //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
+ angularHelper.safeApply($rootScope, function () {
+ authResource.getRemainingTimeoutSeconds().then(function (result) {
+ setUserTimeoutInternal(result);
+ });
+ });
+ }
+ }
+
+ //recurse the countdown!
+ countdownUserTimeout();
+ }
+ else {
+
+ //we are either timed out or very close to timing out so we need to show the login dialog.
+ if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) {
+ //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
+ angularHelper.safeApply($rootScope, function () {
+ try {
+ //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we
+ // don't actually care about this result.
+ authResource.getRemainingTimeoutSeconds();
+ }
+ finally {
+ userAuthExpired();
+ }
+ });
+ }
+ else {
+ //we've got less than 30 seconds remaining so let's check the server
+
+ if (lastServerTimeoutSet != null) {
+ //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
+ // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
+ lastServerTimeoutSet = null;
+
+ //now go get it from the server
+ //NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
+ angularHelper.safeApply($rootScope, function () {
+ authResource.getRemainingTimeoutSeconds().then(function (result) {
+ setUserTimeoutInternal(result);
+ });
+ });
+ }
+
+ //recurse the countdown!
+ countdownUserTimeout();
+
+ }
+ }
+ }
+ }, 5000, //every 5 seconds
+ false); //false = do NOT execute a digest for every iteration
+ }
+
+ /** Called to update the current user's timeout */
+ function setUserTimeoutInternal(newTimeout) {
+
+
+ var asNumber = parseFloat(newTimeout);
+ if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
+ currentUser.remainingAuthSeconds = newTimeout;
+ lastServerTimeoutSet = new Date();
+ }
+ }
+
+ /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */
+ function userAuthExpired(isLogout) {
+ //store the last user id and clear the user
+ if (currentUser && currentUser.id !== undefined) {
+ lastUserId = currentUser.id;
+ }
+
+ if (currentUser) {
+ currentUser.remainingAuthSeconds = 0;
+ }
+
+ lastServerTimeoutSet = null;
+ currentUser = null;
+
+ //broadcast a global event that the user is no longer logged in
+ eventsService.emit("app.notAuthenticated");
+
+ openLoginDialog(isLogout === undefined ? true : !isLogout);
+ }
+
+ // Register a handler for when an item is added to the retry queue
+ securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) {
+ if (securityRetryQueue.hasMore()) {
+ userAuthExpired();
+ }
+ });
+
+ return {
+
+ /** Internal method to display the login dialog */
+ _showLoginDialog: function () {
+ openLoginDialog();
+ },
+
+ /** Returns a promise, sends a request to the server to check if the current cookie is authorized */
+ isAuthenticated: function () {
+ //if we've got a current user then just return true
+ if (currentUser) {
+ var deferred = $q.defer();
+ deferred.resolve(true);
+ return deferred.promise;
+ }
+ return authResource.isAuthenticated();
+ },
+
+ /** Returns a promise, sends a request to the server to validate the credentials */
+ authenticate: function (login, password) {
+
+ return authResource.performLogin(login, password)
+ .then(function (data) {
+
+ //when it's successful, return the user data
+ setCurrentUser(data);
+
+ var result = { user: data, authenticated: true, lastUserId: lastUserId };
+
+ //broadcast a global event
+ eventsService.emit("app.authenticated", result);
+ return result;
+ });
+ },
+
+ /** Logs the user out
+ */
+ logout: function () {
+
+ return authResource.performLogout()
+ .then(function(data) {
+ userAuthExpired();
+ //done!
+ return null;
+ });
+ },
+
+ /** Returns the current user object in a promise */
+ getCurrentUser: function (args) {
+ var deferred = $q.defer();
+
+ if (!currentUser) {
+ authResource.getCurrentUser()
+ .then(function (data) {
+
+ var result = { user: data, authenticated: true, lastUserId: lastUserId };
+
+ //TODO: This is a mega backwards compatibility hack... These variables SHOULD NOT exist in the server variables
+ // since they are not supposed to be dynamic but I accidentally added them there in 7.1.5 IIRC so some people might
+ // now be relying on this :(
+ if (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables) {
+ Umbraco.Sys.ServerVariables["security"] = {
+ startContentId: data.startContentId,
+ startMediaId: data.startMediaId
+ };
+ }
+
+ if (args && args.broadcastEvent) {
+ //broadcast a global event, will inform listening controllers to load in the user specific data
+ eventsService.emit("app.authenticated", result);
+ }
+
+ setCurrentUser(data);
+
+ deferred.resolve(currentUser);
+ });
+
+ }
+ else {
+ deferred.resolve(currentUser);
+ }
+
+ return deferred.promise;
+ },
+
+ /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
+ setUserTimeout: function (newTimeout) {
+ setUserTimeoutInternal(newTimeout);
+ }
+ };
+
+ });
+
+/*Contains multiple services for various helper tasks */
+function versionHelper() {
+
+ return {
+
+ //see: https://gist.github.com/TheDistantSea/8021359
+ versionCompare: function(v1, v2, options) {
+ var lexicographical = options && options.lexicographical,
+ zeroExtend = options && options.zeroExtend,
+ v1parts = v1.split('.'),
+ v2parts = v2.split('.');
+
+ function isValidPart(x) {
+ return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
+ }
+
+ if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
+ return NaN;
+ }
+
+ if (zeroExtend) {
+ while (v1parts.length < v2parts.length) {
+ v1parts.push("0");
+ }
+ while (v2parts.length < v1parts.length) {
+ v2parts.push("0");
+ }
+ }
+
+ if (!lexicographical) {
+ v1parts = v1parts.map(Number);
+ v2parts = v2parts.map(Number);
+ }
+
+ for (var i = 0; i < v1parts.length; ++i) {
+ if (v2parts.length === i) {
+ return 1;
+ }
+
+ if (v1parts[i] === v2parts[i]) {
+ continue;
+ }
+ else if (v1parts[i] > v2parts[i]) {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+ }
+
+ if (v1parts.length !== v2parts.length) {
+ return -1;
+ }
+
+ return 0;
+ }
+ };
+}
+angular.module('umbraco.services').factory('versionHelper', versionHelper);
+
+function dateHelper() {
+
+ return {
+
+ convertToServerStringTime: function(momentLocal, serverOffsetMinutes, format) {
+
+ //get the formatted offset time in HH:mm (server time offset is in minutes)
+ var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") +
+ moment()
+ .startOf('day')
+ .minutes(Math.abs(serverOffsetMinutes))
+ .format('HH:mm');
+
+ var server = moment.utc(momentLocal).utcOffset(formattedOffset);
+ return server.format(format ? format : "YYYY-MM-DD HH:mm:ss");
+ },
+
+ convertToLocalMomentTime: function (strVal, serverOffsetMinutes) {
+
+ //get the formatted offset time in HH:mm (server time offset is in minutes)
+ var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") +
+ moment()
+ .startOf('day')
+ .minutes(Math.abs(serverOffsetMinutes))
+ .format('HH:mm');
+
+ //convert to the iso string format
+ var isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset;
+
+ //create a moment with the iso format which will include the offset with the correct time
+ // then convert it to local time
+ return moment.parseZone(isoFormat).local();
+ }
+
+ };
+}
+angular.module('umbraco.services').factory('dateHelper', dateHelper);
+
+function packageHelper(assetsService, treeService, eventsService, $templateCache) {
+
+ return {
+
+ /** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */
+ packageInstalled: function () {
+
+ //clears the tree
+ treeService.clearCache();
+
+ //clears the template cache
+ $templateCache.removeAll();
+
+ //emit event to notify anything else
+ eventsService.emit("app.reInitialize");
+ }
+
+ };
+}
+angular.module('umbraco.services').factory('packageHelper', packageHelper);
+
+//TODO: I believe this is obsolete
+function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, mediaHelper, umbRequestHelper) {
+ return {
+ /** sets the image's url, thumbnail and if its a folder */
+ setImageData: function(img) {
+
+ img.isFolder = !mediaHelper.hasFilePropertyType(img);
+
+ if(!img.isFolder){
+ img.thumbnail = mediaHelper.resolveFile(img, true);
+ img.image = mediaHelper.resolveFile(img, false);
+ }
+ },
+
+ /** sets the images original size properties - will check if it is a folder and if so will just make it square */
+ setOriginalSize: function(img, maxHeight) {
+ //set to a square by default
+ img.originalWidth = maxHeight;
+ img.originalHeight = maxHeight;
+
+ var widthProp = _.find(img.properties, function(v) { return (v.alias === "umbracoWidth"); });
+ if (widthProp && widthProp.value) {
+ img.originalWidth = parseInt(widthProp.value, 10);
+ if (isNaN(img.originalWidth)) {
+ img.originalWidth = maxHeight;
+ }
+ }
+ var heightProp = _.find(img.properties, function(v) { return (v.alias === "umbracoHeight"); });
+ if (heightProp && heightProp.value) {
+ img.originalHeight = parseInt(heightProp.value, 10);
+ if (isNaN(img.originalHeight)) {
+ img.originalHeight = maxHeight;
+ }
+ }
+ },
+
+ /** sets the image style which get's used in the angular markup */
+ setImageStyle: function(img, width, height, rightMargin, bottomMargin) {
+ img.style = { width: width + "px", height: height + "px", "margin-right": rightMargin + "px", "margin-bottom": bottomMargin + "px" };
+ img.thumbStyle = {
+ "background-image": "url('" + img.thumbnail + "')",
+ "background-repeat": "no-repeat",
+ "background-position": "center",
+ "background-size": Math.min(width, img.originalWidth) + "px " + Math.min(height, img.originalHeight) + "px"
+ };
+ },
+
+ /** gets the image's scaled wdith based on the max row height */
+ getScaledWidth: function(img, maxHeight) {
+ var scaled = img.originalWidth * maxHeight / img.originalHeight;
+ return scaled;
+ //round down, we don't want it too big even by half a pixel otherwise it'll drop to the next row
+ //return Math.floor(scaled);
+ },
+
+ /** returns the target row width taking into account how many images will be in the row and removing what the margin is */
+ getTargetWidth: function(imgsPerRow, maxRowWidth, margin) {
+ //take into account the margin, we will have 1 less margin item than we have total images
+ return (maxRowWidth - ((imgsPerRow - 1) * margin));
+ },
+
+ /**
+ This will determine the row/image height for the next collection of images which takes into account the
+ ideal image count per row. It will check if a row can be filled with this ideal count and if not - if there
+ are additional images available to fill the row it will keep calculating until they fit.
+
+ It will return the calculated height and the number of images for the row.
+
+ targetHeight = optional;
+ */
+ getRowHeightForImages: function(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, targetHeight) {
+
+ var idealImages = imgs.slice(0, idealImgPerRow);
+ //get the target row width without margin
+ var targetRowWidth = this.getTargetWidth(idealImages.length, maxRowWidth, margin);
+ //this gets the image with the smallest height which equals the maximum we can scale up for this image block
+ var maxScaleableHeight = this.getMaxScaleableHeight(idealImages, maxRowHeight);
+ //if the max scale height is smaller than the min display height, we'll use the min display height
+ targetHeight = targetHeight !== undefined ? targetHeight : Math.max(maxScaleableHeight, minDisplayHeight);
+
+ var attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight);
+
+ if (attemptedRowHeight != null) {
+
+ //if this is smaller than the min display then we need to use the min display,
+ // which means we'll need to remove one from the row so we can scale up to fill the row
+ if (attemptedRowHeight < minDisplayHeight) {
+
+ if (idealImages.length > 1) {
+
+ //we'll generate a new targetHeight that is halfway between the max and the current and recurse, passing in a new targetHeight
+ targetHeight += Math.floor((maxRowHeight - targetHeight) / 2);
+ return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin, targetHeight);
+ }
+ else {
+ //this will occur when we only have one image remaining in the row but it's still going to be too wide even when
+ // using the minimum display height specified. In this case we're going to have to just crop the image in it's center
+ // using the minimum display height and the full row width
+ return { height: minDisplayHeight, imgCount: 1 };
+ }
+ }
+ else {
+ //success!
+ return { height: attemptedRowHeight, imgCount: idealImages.length };
+ }
+ }
+
+ //we know the width will fit in a row, but we now need to figure out if we can fill
+ // the entire row in the case that we have more images remaining than the idealImgPerRow.
+
+ if (idealImages.length === imgs.length) {
+ //we have no more remaining images to fill the space, so we'll just use the calc height
+ return { height: targetHeight, imgCount: idealImages.length };
+ }
+ else if (idealImages.length === 1) {
+ //this will occur when we only have one image remaining in the row to process but it's not really going to fit ideally
+ // in the row.
+ return { height: minDisplayHeight, imgCount: 1 };
+ }
+ else if (idealImages.length === idealImgPerRow && targetHeight < maxRowHeight) {
+
+ //if we're already dealing with the ideal images per row and it's not quite wide enough, we can scale up a little bit so
+ // long as the targetHeight is currently less than the maxRowHeight. The scale up will be half-way between our current
+ // target height and the maxRowHeight (we won't loop forever though - if there's a difference of 5 px we'll just quit)
+
+ while (targetHeight < maxRowHeight && (maxRowHeight - targetHeight) > 5) {
+ targetHeight += Math.floor((maxRowHeight - targetHeight) / 2);
+ attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight);
+ if (attemptedRowHeight != null) {
+ //success!
+ return { height: attemptedRowHeight, imgCount: idealImages.length };
+ }
+ }
+
+ //Ok, we couldn't actually scale it up with the ideal row count we'll just recurse with a lesser image count.
+ return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin);
+ }
+ else if (targetHeight === maxRowHeight) {
+
+ //This is going to happen when:
+ // * We can fit a list of images in a row, but they come up too short (based on minDisplayHeight)
+ // * Then we'll try to remove an image, but when we try to scale to fit, the width comes up too narrow but the images are already at their
+ // maximum height (maxRowHeight)
+ // * So we're stuck, we cannot precicely fit the current list of images, so we'll render a row that will be max height but won't be wide enough
+ // which is better than rendering a row that is shorter than the minimum since that could be quite small.
+
+ return { height: targetHeight, imgCount: idealImages.length };
+ }
+ else {
+
+ //we have additional images so we'll recurse and add 1 to the idealImgPerRow until it fits
+ return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow + 1, margin);
+ }
+
+ },
+
+ performGetRowHeight: function(idealImages, targetRowWidth, minDisplayHeight, targetHeight) {
+
+ var currRowWidth = 0;
+
+ for (var i = 0; i < idealImages.length; i++) {
+ var scaledW = this.getScaledWidth(idealImages[i], targetHeight);
+ currRowWidth += scaledW;
+ }
+
+ if (currRowWidth > targetRowWidth) {
+ //get the new scaled height to fit
+ var newHeight = targetRowWidth * targetHeight / currRowWidth;
+
+ return newHeight;
+ }
+ else if (idealImages.length === 1 && (currRowWidth <= targetRowWidth) && !idealImages[0].isFolder) {
+ //if there is only one image, then return the target height
+ return targetHeight;
+ }
+ else if (currRowWidth / targetRowWidth > 0.90) {
+ //it's close enough, it's at least 90% of the width so we'll accept it with the target height
+ return targetHeight;
+ }
+ else {
+ //if it's not successful, return null
+ return null;
+ }
+ },
+
+ /** builds an image grid row */
+ buildRow: function(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, totalRemaining) {
+ var currRowWidth = 0;
+ var row = { images: [] };
+
+ var imageRowHeight = this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin);
+ var targetWidth = this.getTargetWidth(imageRowHeight.imgCount, maxRowWidth, margin);
+
+ var sizes = [];
+ //loop through the images we know fit into the height
+ for (var i = 0; i < imageRowHeight.imgCount; i++) {
+ //get the lower width to ensure it always fits
+ var scaledWidth = Math.floor(this.getScaledWidth(imgs[i], imageRowHeight.height));
+
+ if (currRowWidth + scaledWidth <= targetWidth) {
+ currRowWidth += scaledWidth;
+ sizes.push({
+ width:scaledWidth,
+ //ensure that the height is rounded
+ height: Math.round(imageRowHeight.height)
+ });
+ row.images.push(imgs[i]);
+ }
+ else if (imageRowHeight.imgCount === 1 && row.images.length === 0) {
+ //the image is simply too wide, we'll crop/center it
+ sizes.push({
+ width: maxRowWidth,
+ //ensure that the height is rounded
+ height: Math.round(imageRowHeight.height)
+ });
+ row.images.push(imgs[i]);
+ }
+ else {
+ //the max width has been reached
+ break;
+ }
+ }
+
+ //loop through the images for the row and apply the styles
+ for (var j = 0; j < row.images.length; j++) {
+ var bottomMargin = margin;
+ //make the margin 0 for the last one
+ if (j === (row.images.length - 1)) {
+ margin = 0;
+ }
+ this.setImageStyle(row.images[j], sizes[j].width, sizes[j].height, margin, bottomMargin);
+ }
+
+ if (row.images.length === 1 && totalRemaining > 1) {
+ //if there's only one image on the row and there are more images remaining, set the container to max width
+ row.images[0].style.width = maxRowWidth + "px";
+ }
+
+
+ return row;
+ },
+
+ /** Returns the maximum image scaling height for the current image collection */
+ getMaxScaleableHeight: function(imgs, maxRowHeight) {
+
+ var smallestHeight = _.min(imgs, function(item) { return item.originalHeight; }).originalHeight;
+
+ //adjust the smallestHeight if it is larger than the static max row height
+ if (smallestHeight > maxRowHeight) {
+ smallestHeight = maxRowHeight;
+ }
+ return smallestHeight;
+ },
+
+ /** Creates the image grid with calculated widths/heights for images to fill the grid nicely */
+ buildGrid: function(images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin,imagesOnly) {
+
+ var rows = [];
+ var imagesProcessed = 0;
+
+ //first fill in all of the original image sizes and URLs
+ for (var i = startingIndex; i < images.length; i++) {
+ var item = images[i];
+
+ this.setImageData(item);
+ this.setOriginalSize(item, maxRowHeight);
+
+ if(imagesOnly && !item.isFolder && !item.thumbnail){
+ images.splice(i, 1);
+ i--;
+ }
+ }
+
+ while ((imagesProcessed + startingIndex) < images.length) {
+ //get the maxHeight for the current un-processed images
+ var currImgs = images.slice(imagesProcessed);
+
+ //build the row
+ var remaining = images.length - imagesProcessed;
+ var row = this.buildRow(currImgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, remaining);
+ if (row.images.length > 0) {
+ rows.push(row);
+ imagesProcessed += row.images.length;
+ }
+ else {
+
+ if (currImgs.length > 0) {
+ throw "Could not fill grid with all images, images remaining: " + currImgs.length;
+ }
+
+ //if there was nothing processed, exit
+ break;
+ }
+ }
+
+ return rows;
+ }
+ };
+}
+angular.module("umbraco.services").factory("umbPhotoFolderHelper", umbPhotoFolderHelper);
+
+/**
+ * @ngdoc function
+ * @name umbraco.services.umbModelMapper
+ * @function
+ *
+ * @description
+ * Utility class to map/convert models
+ */
+function umbModelMapper() {
+
+ return {
+
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.umbModelMapper#convertToEntityBasic
+ * @methodOf umbraco.services.umbModelMapper
+ * @function
+ *
+ * @description
+ * Converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model.
+ * @param {Object} source The source model
+ * @param {Number} source.id The node id of the model
+ * @param {String} source.name The node name
+ * @param {String} source.icon The models icon as a css class (.icon-doc)
+ * @param {Number} source.parentId The parentID, if no parent, set to -1
+ * @param {path} source.path comma-separated string of ancestor IDs (-1,1234,1782,1234)
+ */
+
+ /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */
+ convertToEntityBasic: function (source) {
+ var required = ["id", "name", "icon", "parentId", "path"];
+ _.each(required, function (k) {
+ if (!_.has(source, k)) {
+ throw "The source object does not contain the property " + k;
+ }
+ });
+ var optional = ["metaData", "key", "alias"];
+ //now get the basic object
+ var result = _.pick(source, required.concat(optional));
+ return result;
+ }
+
+ };
+}
+angular.module('umbraco.services').factory('umbModelMapper', umbModelMapper);
+
+/**
+ * @ngdoc function
+ * @name umbraco.services.umbSessionStorage
+ * @function
+ *
+ * @description
+ * Used to get/set things in browser sessionStorage but always prefixes keys with "umb_" and converts json vals so there is no overlap
+ * with any sessionStorage created by a developer.
+ */
+function umbSessionStorage($window) {
+
+ //gets the sessionStorage object if available, otherwise just uses a normal object
+ // - required for unit tests.
+ var storage = $window['sessionStorage'] ? $window['sessionStorage'] : {};
+
+ return {
+
+ get: function (key) {
+ return angular.fromJson(storage["umb_" + key]);
+ },
+
+ set : function(key, value) {
+ storage["umb_" + key] = angular.toJson(value);
+ }
+
+ };
+}
+angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorage);
+
+/**
+ * @ngdoc function
+ * @name umbraco.services.updateChecker
+ * @function
+ *
+ * @description
+ * used to check for updates and display a notifcation
+ */
+function updateChecker($http, umbRequestHelper) {
+ return {
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.updateChecker#check
+ * @methodOf umbraco.services.updateChecker
+ * @function
+ *
+ * @description
+ * Called to load in the legacy tree js which is required on startup if a user is logged in or
+ * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded.
+ */
+ check: function() {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "updateCheckApiBaseUrl",
+ "GetCheck")),
+ 'Failed to retrieve update status');
+ }
+ };
+}
+angular.module('umbraco.services').factory('updateChecker', updateChecker);
+
+/**
+* @ngdoc service
+* @name umbraco.services.umbPropertyEditorHelper
+* @description A helper object used for property editors
+**/
+function umbPropEditorHelper() {
+ return {
+ /**
+ * @ngdoc function
+ * @name getImagePropertyValue
+ * @methodOf umbraco.services.umbPropertyEditorHelper
+ * @function
+ *
+ * @description
+ * Returns the correct view path for a property editor, it will detect if it is a full virtual path but if not then default to the internal umbraco one
+ *
+ * @param {string} input the view path currently stored for the property editor
+ */
+ getViewPath: function(input, isPreValue) {
+ var path = String(input);
+
+ if (path.startsWith('/')) {
+
+ //This is an absolute path, so just leave it
+ return path;
+ } else {
+
+ if (path.indexOf("/") >= 0) {
+ //This is a relative path, so just leave it
+ return path;
+ } else {
+ if (!isPreValue) {
+ //i.e. views/propertyeditors/fileupload/fileupload.html
+ return "views/propertyeditors/" + path + "/" + path + ".html";
+ } else {
+ //i.e. views/prevalueeditors/requiredfield.html
+ return "views/prevalueeditors/" + path + ".html";
+ }
+ }
+
+ }
+ }
+ };
+}
+angular.module('umbraco.services').factory('umbPropEditorHelper', umbPropEditorHelper);
+
+
+/**
+* @ngdoc service
+* @name umbraco.services.umbDataFormatter
+* @description A helper object used to format/transform JSON Umbraco data, mostly used for persisting data to the server
+**/
+function umbDataFormatter() {
+ return {
+
+ formatContentTypePostData: function (displayModel, action) {
+
+ //create the save model from the display model
+ var saveModel = _.pick(displayModel,
+ 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes',
+ 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed',
+ 'key', 'parentId', 'alias', 'path');
+
+ //TODO: Map these
+ saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; });
+ saveModel.defaultTemplate = displayModel.defaultTemplate ? displayModel.defaultTemplate.alias : null;
+ var realGroups = _.reject(displayModel.groups, function(g) {
+ //do not include these tabs
+ return g.tabState === "init";
+ });
+ saveModel.groups = _.map(realGroups, function (g) {
+
+ var saveGroup = _.pick(g, 'inherited', 'id', 'sortOrder', 'name');
+
+ var realProperties = _.reject(g.properties, function (p) {
+ //do not include these properties
+ return p.propertyState === "init" || p.inherited === true;
+ });
+
+ var saveProperties = _.map(realProperties, function (p) {
+ var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile');
+ return saveProperty;
+ });
+
+ saveGroup.properties = saveProperties;
+
+ //if this is an inherited group and there are not non-inherited properties on it, then don't send up the data
+ if (saveGroup.inherited === true && saveProperties.length === 0) {
+ return null;
+ }
+
+ return saveGroup;
+ });
+
+ //we don't want any null groups
+ saveModel.groups = _.reject(saveModel.groups, function(g) {
+ return !g;
+ });
+
+ return saveModel;
+ },
+
+ /** formats the display model used to display the data type to the model used to save the data type */
+ formatDataTypePostData: function(displayModel, preValues, action) {
+ var saveModel = {
+ parentId: displayModel.parentId,
+ id: displayModel.id,
+ name: displayModel.name,
+ selectedEditor: displayModel.selectedEditor,
+ //set the action on the save model
+ action: action,
+ preValues: []
+ };
+ for (var i = 0; i < preValues.length; i++) {
+
+ saveModel.preValues.push({
+ key: preValues[i].alias,
+ value: preValues[i].value
+ });
+ }
+ return saveModel;
+ },
+
+ /** formats the display model used to display the member to the model used to save the member */
+ formatMemberPostData: function(displayModel, action) {
+ //this is basically the same as for media but we need to explicitly add the username,email, password to the save model
+
+ var saveModel = this.formatMediaPostData(displayModel, action);
+
+ saveModel.key = displayModel.key;
+
+ var genericTab = _.find(displayModel.tabs, function (item) {
+ return item.id === 0;
+ });
+
+ //map the member login, email, password and groups
+ var propLogin = _.find(genericTab.properties, function (item) {
+ return item.alias === "_umb_login";
+ });
+ var propEmail = _.find(genericTab.properties, function (item) {
+ return item.alias === "_umb_email";
+ });
+ var propPass = _.find(genericTab.properties, function (item) {
+ return item.alias === "_umb_password";
+ });
+ var propGroups = _.find(genericTab.properties, function (item) {
+ return item.alias === "_umb_membergroup";
+ });
+ saveModel.email = propEmail.value;
+ saveModel.username = propLogin.value;
+ saveModel.password = propPass.value;
+
+ var selectedGroups = [];
+ for (var n in propGroups.value) {
+ if (propGroups.value[n] === true) {
+ selectedGroups.push(n);
+ }
+ }
+ saveModel.memberGroups = selectedGroups;
+
+ //turn the dictionary into an array of pairs
+ var memberProviderPropAliases = _.pairs(displayModel.fieldConfig);
+ _.each(displayModel.tabs, function (tab) {
+ _.each(tab.properties, function (prop) {
+ var foundAlias = _.find(memberProviderPropAliases, function(item) {
+ return prop.alias === item[1];
+ });
+ if (foundAlias) {
+ //we know the current property matches an alias, now we need to determine which membership provider property it was for
+ // by looking at the key
+ switch (foundAlias[0]) {
+ case "umbracoMemberLockedOut":
+ saveModel.isLockedOut = prop.value.toString() === "1" ? true : false;
+ break;
+ case "umbracoMemberApproved":
+ saveModel.isApproved = prop.value.toString() === "1" ? true : false;
+ break;
+ case "umbracoMemberComments":
+ saveModel.comments = prop.value;
+ break;
+ }
+ }
+ });
+ });
+
+
+
+ return saveModel;
+ },
+
+ /** formats the display model used to display the media to the model used to save the media */
+ formatMediaPostData: function(displayModel, action) {
+ //NOTE: the display model inherits from the save model so we can in theory just post up the display model but
+ // we don't want to post all of the data as it is unecessary.
+ var saveModel = {
+ id: displayModel.id,
+ properties: [],
+ name: displayModel.name,
+ contentTypeAlias: displayModel.contentTypeAlias,
+ parentId: displayModel.parentId,
+ //set the action on the save model
+ action: action
+ };
+
+ _.each(displayModel.tabs, function (tab) {
+
+ _.each(tab.properties, function (prop) {
+
+ //don't include the custom generic tab properties
+ if (!prop.alias.startsWith("_umb_")) {
+ saveModel.properties.push({
+ id: prop.id,
+ alias: prop.alias,
+ value: prop.value
+ });
+ }
+
+ });
+ });
+
+ return saveModel;
+ },
+
+ /** formats the display model used to display the content to the model used to save the content */
+ formatContentPostData: function (displayModel, action) {
+
+ //this is basically the same as for media but we need to explicitly add some extra properties
+ var saveModel = this.formatMediaPostData(displayModel, action);
+
+ var genericTab = _.find(displayModel.tabs, function (item) {
+ return item.id === 0;
+ });
+
+ var propExpireDate = _.find(genericTab.properties, function(item) {
+ return item.alias === "_umb_expiredate";
+ });
+ var propReleaseDate = _.find(genericTab.properties, function (item) {
+ return item.alias === "_umb_releasedate";
+ });
+ var propTemplate = _.find(genericTab.properties, function (item) {
+ return item.alias === "_umb_template";
+ });
+ saveModel.expireDate = propExpireDate.value;
+ saveModel.releaseDate = propReleaseDate.value;
+ saveModel.templateAlias = propTemplate.value;
+
+ return saveModel;
+ }
+ };
+}
+angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter);
+
+
+
+/**
+ * @ngdoc service
+ * @name umbraco.services.windowResizeListener
+ * @function
+ *
+ * @description
+ * A single window resize listener... we don't want to have more than one in theory to ensure that
+ * there aren't too many events raised. This will debounce the event with 100 ms intervals and force
+ * a $rootScope.$apply when changed and notify all listeners
+ *
+ */
+function windowResizeListener($rootScope) {
+
+ var WinReszier = (function () {
+ var registered = [];
+ var inited = false;
+ var resize = _.debounce(function(ev) {
+ notify();
+ }, 100);
+ var notify = function () {
+ var h = $(window).height();
+ var w = $(window).width();
+ //execute all registrations inside of a digest
+ $rootScope.$apply(function() {
+ for (var i = 0, cnt = registered.length; i < cnt; i++) {
+ registered[i].apply($(window), [{ width: w, height: h }]);
+ }
+ });
+ };
+ return {
+ register: function (fn) {
+ registered.push(fn);
+ if (inited === false) {
+ $(window).bind('resize', resize);
+ inited = true;
+ }
+ },
+ unregister: function (fn) {
+ var index = registered.indexOf(fn);
+ if (index > -1) {
+ registered.splice(index, 1);
+ }
+ }
+ };
+ }());
+
+ return {
+
+ /**
+ * Register a callback for resizing
+ * @param {Function} cb
+ */
+ register: function (cb) {
+ WinReszier.register(cb);
+ },
+
+ /**
+ * Removes a registered callback
+ * @param {Function} cb
+ */
+ unregister: function(cb) {
+ WinReszier.unregister(cb);
+ }
+
+ };
+}
+angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener);
+/**
+ * @ngdoc service
+ * @name umbraco.services.xmlhelper
+ * @function
+ *
+ * @description
+ * Used to convert legacy xml data to json and back again
+ */
+function xmlhelper($http) {
+ /*
+ Copyright 2011 Abdulla Abdurakhmanov
+ Original sources are available at https://code.google.com/p/x2js/
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+ function X2JS() {
+ var VERSION = "1.0.11";
+ var escapeMode = false;
+
+ var DOMNodeTypes = {
+ ELEMENT_NODE: 1,
+ TEXT_NODE: 3,
+ CDATA_SECTION_NODE: 4,
+ DOCUMENT_NODE: 9
+ };
+
+ function getNodeLocalName(node) {
+ var nodeLocalName = node.localName;
+ if (nodeLocalName == null) {
+ nodeLocalName = node.baseName;
+ } // Yeah, this is IE!!
+
+ if (nodeLocalName === null || nodeLocalName === "") {
+ nodeLocalName = node.nodeName;
+ } // =="" is IE too
+
+ return nodeLocalName;
+ }
+
+ function getNodePrefix(node) {
+ return node.prefix;
+ }
+
+ function escapeXmlChars(str) {
+ if (typeof (str) === "string") {
+ return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/');
+ } else {
+ return str;
+ }
+ }
+
+ function unescapeXmlChars(str) {
+ return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(///g, '\/');
+ }
+
+ function parseDOMChildren(node) {
+ var result, child, childName;
+
+ if (node.nodeType === DOMNodeTypes.DOCUMENT_NODE) {
+ result = {};
+ child = node.firstChild;
+ childName = getNodeLocalName(child);
+ result[childName] = parseDOMChildren(child);
+ return result;
+ }
+ else {
+
+ if (node.nodeType === DOMNodeTypes.ELEMENT_NODE) {
+ result = {};
+ result.__cnt = 0;
+ var nodeChildren = node.childNodes;
+
+ // Children nodes
+ for (var cidx = 0; cidx < nodeChildren.length; cidx++) {
+ child = nodeChildren.item(cidx); // nodeChildren[cidx];
+ childName = getNodeLocalName(child);
+
+ result.__cnt++;
+ if (result[childName] === null) {
+ result[childName] = parseDOMChildren(child);
+ result[childName + "_asArray"] = new Array(1);
+ result[childName + "_asArray"][0] = result[childName];
+ }
+ else {
+ if (result[childName] !== null) {
+ if (!(result[childName] instanceof Array)) {
+ var tmpObj = result[childName];
+ result[childName] = [];
+ result[childName][0] = tmpObj;
+
+ result[childName + "_asArray"] = result[childName];
+ }
+ }
+ var aridx = 0;
+ while (result[childName][aridx] !== null) {
+ aridx++;
+ }
+
+ (result[childName])[aridx] = parseDOMChildren(child);
+ }
+ }
+
+ // Attributes
+ for (var aidx = 0; aidx < node.attributes.length; aidx++) {
+ var attr = node.attributes.item(aidx); // [aidx];
+ result.__cnt++;
+ result["_" + attr.name] = attr.value;
+ }
+
+ // Node namespace prefix
+ var nodePrefix = getNodePrefix(node);
+ if (nodePrefix !== null && nodePrefix !== "") {
+ result.__cnt++;
+ result.__prefix = nodePrefix;
+ }
+
+ if (result.__cnt === 1 && result["#text"] !== null) {
+ result = result["#text"];
+ }
+
+ if (result["#text"] !== null) {
+ result.__text = result["#text"];
+ if (escapeMode) {
+ result.__text = unescapeXmlChars(result.__text);
+ }
+
+ delete result["#text"];
+ delete result["#text_asArray"];
+ }
+ if (result["#cdata-section"] != null) {
+ result.__cdata = result["#cdata-section"];
+ delete result["#cdata-section"];
+ delete result["#cdata-section_asArray"];
+ }
+
+ if (result.__text != null || result.__cdata != null) {
+ result.toString = function () {
+ return (this.__text != null ? this.__text : '') + (this.__cdata != null ? this.__cdata : '');
+ };
+ }
+ return result;
+ }
+ else {
+ if (node.nodeType === DOMNodeTypes.TEXT_NODE || node.nodeType === DOMNodeTypes.CDATA_SECTION_NODE) {
+ return node.nodeValue;
+ }
+ }
+ }
+ }
+
+ function startTag(jsonObj, element, attrList, closed) {
+ var resultStr = "<" + ((jsonObj != null && jsonObj.__prefix != null) ? (jsonObj.__prefix + ":") : "") + element;
+ if (attrList != null) {
+ for (var aidx = 0; aidx < attrList.length; aidx++) {
+ var attrName = attrList[aidx];
+ var attrVal = jsonObj[attrName];
+ resultStr += " " + attrName.substr(1) + "='" + attrVal + "'";
+ }
+ }
+ if (!closed) {
+ resultStr += ">";
+ } else {
+ resultStr += "/>";
+ }
+
+ return resultStr;
+ }
+
+ function endTag(jsonObj, elementName) {
+ return "" + (jsonObj.__prefix !== null ? (jsonObj.__prefix + ":") : "") + elementName + ">";
+ }
+
+ function endsWith(str, suffix) {
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;
+ }
+
+ function jsonXmlSpecialElem(jsonObj, jsonObjField) {
+ if (endsWith(jsonObjField.toString(), ("_asArray")) || jsonObjField.toString().indexOf("_") === 0 || (jsonObj[jsonObjField] instanceof Function)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function jsonXmlElemCount(jsonObj) {
+ var elementsCnt = 0;
+ if (jsonObj instanceof Object) {
+ for (var it in jsonObj) {
+ if (jsonXmlSpecialElem(jsonObj, it)) {
+ continue;
+ }
+ elementsCnt++;
+ }
+ }
+ return elementsCnt;
+ }
+
+ function parseJSONAttributes(jsonObj) {
+ var attrList = [];
+ if (jsonObj instanceof Object) {
+ for (var ait in jsonObj) {
+ if (ait.toString().indexOf("__") === -1 && ait.toString().indexOf("_") === 0) {
+ attrList.push(ait);
+ }
+ }
+ }
+
+ return attrList;
+ }
+
+ function parseJSONTextAttrs(jsonTxtObj) {
+ var result = "";
+
+ if (jsonTxtObj.__cdata != null) {
+ result += "";
+ }
+
+ if (jsonTxtObj.__text != null) {
+ if (escapeMode) {
+ result += escapeXmlChars(jsonTxtObj.__text);
+ } else {
+ result += jsonTxtObj.__text;
+ }
+ }
+ return result;
+ }
+
+ function parseJSONTextObject(jsonTxtObj) {
+ var result = "";
+
+ if (jsonTxtObj instanceof Object) {
+ result += parseJSONTextAttrs(jsonTxtObj);
+ }
+ else {
+ if (jsonTxtObj != null) {
+ if (escapeMode) {
+ result += escapeXmlChars(jsonTxtObj);
+ } else {
+ result += jsonTxtObj;
+ }
+ }
+ }
+
+
+ return result;
+ }
+
+ function parseJSONArray(jsonArrRoot, jsonArrObj, attrList) {
+ var result = "";
+ if (jsonArrRoot.length === 0) {
+ result += startTag(jsonArrRoot, jsonArrObj, attrList, true);
+ }
+ else {
+ for (var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) {
+ result += startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false);
+ result += parseJSONObject(jsonArrRoot[arIdx]);
+ result += endTag(jsonArrRoot[arIdx], jsonArrObj);
+ }
+ }
+ return result;
+ }
+
+ function parseJSONObject(jsonObj) {
+ var result = "";
+
+ var elementsCnt = jsonXmlElemCount(jsonObj);
+
+ if (elementsCnt > 0) {
+ for (var it in jsonObj) {
+ if (jsonXmlSpecialElem(jsonObj, it)) {
+ continue;
+ }
+
+ var subObj = jsonObj[it];
+ var attrList = parseJSONAttributes(subObj);
+
+ if (subObj === null || subObj === undefined) {
+ result += startTag(subObj, it, attrList, true);
+ } else {
+ if (subObj instanceof Object) {
+
+ if (subObj instanceof Array) {
+ result += parseJSONArray(subObj, it, attrList);
+ } else {
+ var subObjElementsCnt = jsonXmlElemCount(subObj);
+ if (subObjElementsCnt > 0 || subObj.__text !== null || subObj.__cdata !== null) {
+ result += startTag(subObj, it, attrList, false);
+ result += parseJSONObject(subObj);
+ result += endTag(subObj, it);
+ } else {
+ result += startTag(subObj, it, attrList, true);
+ }
+ }
+
+ } else {
+ result += startTag(subObj, it, attrList, false);
+ result += parseJSONTextObject(subObj);
+ result += endTag(subObj, it);
+ }
+ }
+ }
+ }
+ result += parseJSONTextObject(jsonObj);
+
+ return result;
+ }
+
+ this.parseXmlString = function (xmlDocStr) {
+ var xmlDoc;
+ if (window.DOMParser) {
+ var parser = new window.DOMParser();
+ xmlDoc = parser.parseFromString(xmlDocStr, "text/xml");
+ }
+ else {
+ // IE :(
+ if (xmlDocStr.indexOf("") === 0) {
+ xmlDocStr = xmlDocStr.substr(xmlDocStr.indexOf("?>") + 2);
+ }
+ xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
+ xmlDoc.async = "false";
+ xmlDoc.loadXML(xmlDocStr);
+ }
+ return xmlDoc;
+ };
+
+ this.xml2json = function (xmlDoc) {
+ return parseDOMChildren(xmlDoc);
+ };
+
+ this.xml_str2json = function (xmlDocStr) {
+ var xmlDoc = this.parseXmlString(xmlDocStr);
+ return this.xml2json(xmlDoc);
+ };
+
+ this.json2xml_str = function (jsonObj) {
+ return parseJSONObject(jsonObj);
+ };
+
+ this.json2xml = function (jsonObj) {
+ var xmlDocStr = this.json2xml_str(jsonObj);
+ return this.parseXmlString(xmlDocStr);
+ };
+
+ this.getVersion = function () {
+ return VERSION;
+ };
+
+ this.escapeMode = function (enabled) {
+ escapeMode = enabled;
+ };
+ }
+
+ var x2js = new X2JS();
+ return {
+ /** Called to load in the legacy tree js which is required on startup if a user is logged in or
+ after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. */
+ toJson: function (xml) {
+ var json = x2js.xml_str2json(xml);
+ return json;
+ },
+ fromJson: function (json) {
+ var xml = x2js.json2xml_str(json);
+ return xml;
+ }
+ };
+}
+angular.module('umbraco.services').factory('xmlhelper', xmlhelper);
+
+})();
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbraco.testing.js b/WebCms/Umbraco/Js/umbraco.testing.js
new file mode 100644
index 0000000..c0e445a
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbraco.testing.js
@@ -0,0 +1,2285 @@
+/*! umbraco
+ * https://github.com/umbraco/umbraco-cms/
+ * Copyright (c) 2016 Umbraco HQ;
+ * Licensed
+ */
+
+(function() {
+
+angular.module("umbraco.mocks", ['ngCookies']);
+angular.module("umbraco.mocks.services", []);
+angular.module('umbraco.mocks').
+ factory('prevaluesMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) {
+ 'use strict';
+
+ function getRichTextConfiguration(status, data, headers) {
+ if (!mocksUtils.checkAuth()) {
+ return [401, null, null];
+ }
+ else {
+ return [200, { "plugins": [{ "name": "code", "useOnFrontend": true }, { "name": "paste", "useOnFrontend": true }, { "name": "umbracolink", "useOnFrontend": true }], "commands": [{ "icon": "images/editor/code.gif", "command": "code", "alias": "code", "userInterface": "false", "frontEndCommand": "code", "value": "", "priority": 1, "isStylePicker": false }, { "icon": "images/editor/removeformat.gif", "command": "removeformat", "alias": "removeformat", "userInterface": "false", "frontEndCommand": "removeformat", "value": "", "priority": 2, "isStylePicker": false }, { "icon": "images/editor/undo.gif", "command": "undo", "alias": "undo", "userInterface": "false", "frontEndCommand": "undo", "value": "", "priority": 11, "isStylePicker": false }, { "icon": "images/editor/redo.gif", "command": "redo", "alias": "redo", "userInterface": "false", "frontEndCommand": "redo", "value": "", "priority": 12, "isStylePicker": false }, { "icon": "images/editor/cut.gif", "command": "cut", "alias": "cut", "userInterface": "false", "frontEndCommand": "cut", "value": "", "priority": 13, "isStylePicker": false }, { "icon": "images/editor/copy.gif", "command": "copy", "alias": "copy", "userInterface": "false", "frontEndCommand": "copy", "value": "", "priority": 14, "isStylePicker": false }, { "icon": "images/editor/showStyles.png", "command": "styleselect", "alias": "styleselect", "userInterface": "false", "frontEndCommand": "styleselect", "value": "", "priority": 20, "isStylePicker": false }, { "icon": "images/editor/bold.gif", "command": "bold", "alias": "bold", "userInterface": "false", "frontEndCommand": "bold", "value": "", "priority": 21, "isStylePicker": false }, { "icon": "images/editor/italic.gif", "command": "italic", "alias": "italic", "userInterface": "false", "frontEndCommand": "italic", "value": "", "priority": 22, "isStylePicker": false }, { "icon": "images/editor/underline.gif", "command": "underline", "alias": "underline", "userInterface": "false", "frontEndCommand": "underline", "value": "", "priority": 23, "isStylePicker": false }, { "icon": "images/editor/strikethrough.gif", "command": "strikethrough", "alias": "strikethrough", "userInterface": "false", "frontEndCommand": "strikethrough", "value": "", "priority": 24, "isStylePicker": false }, { "icon": "images/editor/justifyleft.gif", "command": "justifyleft", "alias": "justifyleft", "userInterface": "false", "frontEndCommand": "alignleft", "value": "", "priority": 31, "isStylePicker": false }, { "icon": "images/editor/justifycenter.gif", "command": "justifycenter", "alias": "justifycenter", "userInterface": "false", "frontEndCommand": "aligncenter", "value": "", "priority": 32, "isStylePicker": false }, { "icon": "images/editor/justifyright.gif", "command": "justifyright", "alias": "justifyright", "userInterface": "false", "frontEndCommand": "alignright", "value": "", "priority": 33, "isStylePicker": false }, { "icon": "images/editor/justifyfull.gif", "command": "justifyfull", "alias": "justifyfull", "userInterface": "false", "frontEndCommand": "alignfull", "value": "", "priority": 34, "isStylePicker": false }, { "icon": "images/editor/bullist.gif", "command": "bullist", "alias": "bullist", "userInterface": "false", "frontEndCommand": "bullist", "value": "", "priority": 41, "isStylePicker": false }, { "icon": "images/editor/numlist.gif", "command": "numlist", "alias": "numlist", "userInterface": "false", "frontEndCommand": "numlist", "value": "", "priority": 42, "isStylePicker": false }, { "icon": "images/editor/outdent.gif", "command": "outdent", "alias": "outdent", "userInterface": "false", "frontEndCommand": "outdent", "value": "", "priority": 43, "isStylePicker": false }, { "icon": "images/editor/indent.gif", "command": "indent", "alias": "indent", "userInterface": "false", "frontEndCommand": "indent", "value": "", "priority": 44, "isStylePicker": false }, { "icon": "images/editor/link.gif", "command": "link", "alias": "mcelink", "userInterface": "true", "frontEndCommand": "link", "value": "", "priority": 51, "isStylePicker": false }, { "icon": "images/editor/unLink.gif", "command": "unlink", "alias": "unlink", "userInterface": "false", "frontEndCommand": "unlink", "value": "", "priority": 52, "isStylePicker": false }, { "icon": "images/editor/anchor.gif", "command": "anchor", "alias": "mceinsertanchor", "userInterface": "false", "frontEndCommand": "anchor", "value": "", "priority": 53, "isStylePicker": false }, { "icon": "images/editor/image.gif", "command": "image", "alias": "mceimage", "userInterface": "true", "frontEndCommand": "umbmediapicker", "value": "", "priority": 61, "isStylePicker": false }, { "icon": "images/editor/insMacro.gif", "command": "umbracomacro", "alias": "umbracomacro", "userInterface": "true", "frontEndCommand": "umbmacro", "value": "", "priority": 62, "isStylePicker": false }, { "icon": "images/editor/table.gif", "command": "table", "alias": "mceinserttable", "userInterface": "true", "frontEndCommand": "table", "value": "", "priority": 63, "isStylePicker": false }, { "icon": "images/editor/media.gif", "command": "umbracoembed", "alias": "umbracoembed", "userInterface": "true", "frontEndCommand": "umbembeddialog", "value": "", "priority": 66, "isStylePicker": false }, { "icon": "images/editor/hr.gif", "command": "hr", "alias": "inserthorizontalrule", "userInterface": "false", "frontEndCommand": "hr", "value": "", "priority": 71, "isStylePicker": false }, { "icon": "images/editor/sub.gif", "command": "sub", "alias": "subscript", "userInterface": "false", "frontEndCommand": "sub", "value": "", "priority": 72, "isStylePicker": false }, { "icon": "images/editor/sup.gif", "command": "sup", "alias": "superscript", "userInterface": "false", "frontEndCommand": "sup", "value": "", "priority": 73, "isStylePicker": false }, { "icon": "images/editor/charmap.gif", "command": "charmap", "alias": "mcecharmap", "userInterface": "false", "frontEndCommand": "charmap", "value": "", "priority": 74, "isStylePicker": false }], "validElements": "+a[id|style|rel|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align],-h2[id|dir|class|align],-h3[id|dir|class|align],-h4[id|dir|class|align],-h5[id|dir|class|align],-h6[id|style|dir|class|align],hr[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*]", "inValidElements": "font", "customConfig": { "entity_encoding": "raw" } }, null];
+ }
+ }
+
+ function getCanvasEditorConfiguration(status, data, headers){
+ if (!mocksUtils.checkAuth()) {
+ return [401, null, null];
+ }
+ else {
+ return [200,
+ [
+ {
+ "name": "Rich text editor",
+ "alias": "rte",
+ "view": "rte",
+ "icon": "icon-article"
+ },
+ {
+ "name": "Image",
+ "alias": "media",
+ "view": "media",
+ "icon": "icon-picture"
+ },
+ {
+ "name": "Macro",
+ "alias": "macro",
+ "view": "macro",
+ "icon": "icon-settings-alt"
+ },
+ {
+ "name": "Embed",
+ "alias": "embed",
+ "view": "embed",
+ "icon": "icon-movie-alt"
+ },
+ {
+ "name": "Headline",
+ "alias": "headline",
+ "view": "textstring",
+ "icon": "icon-coin",
+ "config": {
+ "style": "font-size: 36px; line-height: 45px; font-weight: bold",
+ "markup": "
One-level paths in domains are supported, eg. 'example.com/en'. However, they should be avoided. Better use the culture setting above.",
+ "assignDomain_domainUpdated": "Domain '%0%' has been updated",
+ "assignDomain_orEdit": "Edit Current Domains",
+ "assignDomain_inherit": "Inherit",
+ "assignDomain_setLanguage": "Culture",
+ "assignDomain_setLanguageHelp": "Set the culture for nodes below the current node, or inherit culture from parent nodes. Will also apply to the current node, unless a domain below applies too.",
+ "assignDomain_setDomains": "Domains",
+ "auditTrails_atViewingFor": "Viewing for",
+ "buttons_select": "Select",
+ "buttons_somethingElse": "Do something else",
+ "buttons_bold": "Bold",
+ "buttons_deindent": "Cancel Paragraph Indent",
+ "buttons_formFieldInsert": "Insert form field",
+ "buttons_graphicHeadline": "Insert graphic headline",
+ "buttons_htmlEdit": "Edit Html",
+ "buttons_indent": "Indent Paragraph",
+ "buttons_italic": "Italic",
+ "buttons_justifyCenter": "Center",
+ "buttons_justifyLeft": "Justify Left",
+ "buttons_justifyRight": "Justify Right",
+ "buttons_linkInsert": "Insert Link",
+ "buttons_linkLocal": "Insert local link (anchor)",
+ "buttons_listBullet": "Bullet List",
+ "buttons_listNumeric": "Numeric List",
+ "buttons_macroInsert": "Insert macro",
+ "buttons_pictureInsert": "Insert picture",
+ "buttons_relations": "Edit relations",
+ "buttons_save": "Save",
+ "buttons_saveAndPublish": "Save and publish",
+ "buttons_saveToPublish": "Save and send for approval",
+ "buttons_showPage": "Preview",
+ "buttons_showPageDisabled": "Preview is disabled because there's no template assigned",
+ "buttons_styleChoose": "Choose style",
+ "buttons_styleShow": "Show styles",
+ "buttons_tableInsert": "Insert table",
+ "changeDocType_changeDocTypeInstruction": "To change the document type for the selected content, first select from the list of valid types for this location.",
+ "changeDocType_changeDocTypeInstruction2": "Then confirm and/or amend the mapping of properties from the current type to the new, and click Save.",
+ "changeDocType_contentRepublished": "The content has been re-published.",
+ "changeDocType_currentProperty": "Current Property",
+ "changeDocType_currentType": "Current type",
+ "changeDocType_docTypeCannotBeChanged": "The document type cannot be changed, as there are no alternatives valid for this location.",
+ "changeDocType_docTypeChanged": "Document Type Changed",
+ "changeDocType_mapProperties": "Map Properties",
+ "changeDocType_mapToProperty": "Map to Property",
+ "changeDocType_newTemplate": "New Template",
+ "changeDocType_newType": "New Type",
+ "changeDocType_none": "none",
+ "changeDocType_selectedContent": "Content",
+ "changeDocType_selectNewDocType": "Select New Document Type",
+ "changeDocType_successMessage": "The document type of the selected content has been successfully changed to [new type] and the following properties mapped:",
+ "changeDocType_to": "to",
+ "changeDocType_validationErrorPropertyWithMoreThanOneMapping": "Could not complete property mapping as one or more properties have more than one mapping defined.",
+ "changeDocType_validDocTypesNote": "Only alternate types valid for the current location are displayed.",
+ "content_about": "About this page",
+ "content_alias": "Alias",
+ "content_alternativeTextHelp": "(how would you describe the picture over the phone)",
+ "content_alternativeUrls": "Alternative Links",
+ "content_clickToEdit": "Click to edit this item",
+ "content_createBy": "Created by",
+ "content_createByDesc": "Original autho",
+ "content_updatedBy": "Updated by",
+ "content_createDate": "Created",
+ "content_createDateDesc": "Date/time this document was created",
+ "content_documentType": "Document Type",
+ "content_editing": "Editing",
+ "content_expireDate": "Remove at",
+ "content_itemChanged": "This item has been changed after publication",
+ "content_itemNotPublished": "This item is not published",
+ "content_lastPublished": "Last published",
+ "content_listViewNoItems": "There are no items show in the list.",
+ "content_mediatype": "Media Type",
+ "content_mediaLinks": "Link to media item(s)",
+ "content_membergroup": "Member Group",
+ "content_memberrole": "Role",
+ "content_membertype": "Member Type",
+ "content_noDate": "No date chosen",
+ "content_nodeName": "Page Title",
+ "content_otherElements": "Properties",
+ "content_parentNotPublished": "This document is published but is not visible because the parent '%0%' is unpublished",
+ "content_parentNotPublishedAnomaly": "This document is published but is not in the cache",
+ "content_publish": "Publish",
+ "content_publishStatus": "Publication Status",
+ "content_releaseDate": "Publish at",
+ "content_removeDate": "Clear Date",
+ "content_sortDone": "Sortorder is updated",
+ "content_sortHelp": "To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the 'shift' or 'control' key while selecting",
+ "content_statistics": "Statistics",
+ "content_titleOptional": "Title (optional)",
+ "content_type": "Type",
+ "content_unPublish": "Unpublish",
+ "content_updateDate": "Last edited",
+ "content_updateDateDesc": "Date/time this document was created",
+ "content_uploadClear": "Remove file",
+ "content_urls": "Link to document",
+ "content_memberof": "Member of group(s)",
+ "content_notmemberof": "Not a member of group(s)",
+ "content_childItems": "Child items",
+ "create_chooseNode": "Where do you want to create the new %0%",
+ "create_createUnder": "Create a page under",
+ "create_updateData": "Choose a type and a title",
+ "create_noDocumentTypes": "There are no allowed document types available. You must enable these in the settings section under 'document types'.",
+ "create_noMediaTypes": "There are no allowed media types available. You must enable these in the settings section under 'media types'.",
+ "dashboard_browser": "Browse your website",
+ "dashboard_dontShowAgain": "- Hide",
+ "dashboard_nothinghappens": "If Umbraco isn't opening, you might need to allow popups from this site",
+ "dashboard_openinnew": "has opened in a new window",
+ "dashboard_restart": "Restart",
+ "dashboard_visit": "Visit",
+ "dashboard_welcome": "Welcome",
+ "defaultdialogs_anchorInsert": "Name",
+ "defaultdialogs_assignDomain": "Manage hostnames",
+ "defaultdialogs_closeThisWindow": "Close this window",
+ "defaultdialogs_confirmdelete": "Are you sure you want to delete",
+ "defaultdialogs_confirmdisable": "Are you sure you want to disable",
+ "defaultdialogs_confirmEmptyTrashcan": "Please check this box to confirm deletion of %0% item(s)",
+ "defaultdialogs_confirmlogout": "Are you sure?",
+ "defaultdialogs_confirmSure": "Are you sure?",
+ "defaultdialogs_cut": "Cut",
+ "defaultdialogs_editdictionary": "Edit Dictionary Item",
+ "defaultdialogs_editlanguage": "Edit Language",
+ "defaultdialogs_insertAnchor": "Insert local link",
+ "defaultdialogs_insertCharacter": "Insert character",
+ "defaultdialogs_insertgraphicheadline": "Insert graphic headline",
+ "defaultdialogs_insertimage": "Insert picture",
+ "defaultdialogs_insertlink": "Insert link",
+ "defaultdialogs_insertMacro": "Click to add a Macro",
+ "defaultdialogs_inserttable": "Insert table",
+ "defaultdialogs_lastEdited": "Last Edited",
+ "defaultdialogs_link": "Link",
+ "defaultdialogs_linkinternal": "Internal link:",
+ "defaultdialogs_linklocaltip": "When using local links, insert '#' infront of link",
+ "defaultdialogs_linknewwindow": "Open in new window?",
+ "defaultdialogs_macroContainerSettings": "Macro Settings",
+ "defaultdialogs_macroDoesNotHaveProperties": "This macro does not contain any properties you can edit",
+ "defaultdialogs_paste": "Paste",
+ "defaultdialogs_permissionsEdit": "Edit Permissions for",
+ "defaultdialogs_recycleBinDeleting": "The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place",
+ "defaultdialogs_recycleBinIsEmpty": "The recycle bin is now empty",
+ "defaultdialogs_recycleBinWarning": "When items are deleted from the recycle bin, they will be gone forever",
+ "defaultdialogs_regexSearchError": "regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.",
+ "defaultdialogs_regexSearchHelp": "Search for a regular expression to add validation to a form field. Exemple: 'email, 'zip-code' 'url'",
+ "defaultdialogs_removeMacro": "Remove Macro",
+ "defaultdialogs_requiredField": "Required Field",
+ "defaultdialogs_sitereindexed": "Site is reindexed",
+ "defaultdialogs_siterepublished": "The website cache has been refreshed. All publish content is now uptodate. While all unpublished content is still unpublished",
+ "defaultdialogs_siterepublishHelp": "The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished.",
+ "defaultdialogs_tableColumns": "Number of columns",
+ "defaultdialogs_tableRows": "Number of rows",
+ "defaultdialogs_templateContentAreaHelp": "Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, by refering this ID using a <asp:content /> element.",
+ "defaultdialogs_templateContentPlaceHolderHelp": "Select a placeholder id from the list below. You can only choose Id's from the current template's master.",
+ "defaultdialogs_thumbnailimageclickfororiginal": "Click on the image to see full size",
+ "defaultdialogs_treepicker": "Pick item",
+ "defaultdialogs_viewCacheItem": "View Cache Item",
+ "dictionaryItem_description": "Edit the different language versions for the dictionary item '%0%' below You can add additional languages under the 'languages' in the menu on the left ",
+ "dictionaryItem_displayName": "Culture Name",
+ "placeholders_username": "Enter your username",
+ "placeholders_password": "Enter your password",
+ "placeholders_entername": "Enter a name...",
+ "placeholders_nameentity": "Name the %0%...",
+ "placeholders_search": "Type to search...",
+ "placeholders_filter": "Type to filter...",
+ "editcontenttype_allowedchildnodetypes": "Allowed child nodetypes",
+ "editcontenttype_create": "Create",
+ "editcontenttype_deletetab": "Delete tab",
+ "editcontenttype_description": "Description",
+ "editcontenttype_newtab": "New tab",
+ "editcontenttype_tab": "Tab",
+ "editcontenttype_thumbnail": "Thumbnail",
+ "editcontenttype_iscontainercontenttype": "Use as container content type",
+ "editdatatype_addPrevalue": "Add prevalue",
+ "editdatatype_dataBaseDatatype": "Database datatype",
+ "editdatatype_guid": "Property editor GUID",
+ "editdatatype_renderControl": "Property editor",
+ "editdatatype_rteButtons": "Buttons",
+ "editdatatype_rteEnableAdvancedSettings": "Enable advanced settings for",
+ "editdatatype_rteEnableContextMenu": "Enable context menu",
+ "editdatatype_rteMaximumDefaultImgSize": "Maximum default size of inserted images",
+ "editdatatype_rteRelatedStylesheets": "Related stylesheets",
+ "editdatatype_rteShowLabel": "Show label",
+ "editdatatype_rteWidthAndHeight": "Width and height",
+ "errorHandling_errorButDataWasSaved": "Your data has been saved, but before you can publish this page there are some errors you need to fix first:",
+ "errorHandling_errorChangingProviderPassword": "The current MemberShip Provider does not support changing password (EnablePasswordRetrieval need to be true)",
+ "errorHandling_errorExistsWithoutTab": "%0% already exists",
+ "errorHandling_errorHeader": "There were errors:",
+ "errorHandling_errorHeaderWithoutTab": "There were errors:",
+ "errorHandling_errorInPasswordFormat": "The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s)",
+ "errorHandling_errorIntegerWithoutTab": "%0% must be an integer",
+ "errorHandling_errorMandatory": "The %0% field in the %1% tab is mandatory",
+ "errorHandling_errorMandatoryWithoutTab": "%0% is a mandatory field",
+ "errorHandling_errorRegExp": "%0% at %1% is not in a correct format",
+ "errorHandling_errorRegExpWithoutTab": "%0% is not in a correct format",
+ "errors_dissallowedMediaType": "The specified file type has been dissallowed by the administrator",
+ "errors_codemirroriewarning": "NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough.",
+ "errors_contentTypeAliasAndNameNotNull": "Please fill both alias and name on the new propertytype!",
+ "errors_filePermissionsError": "There is a problem with read/write access to a specific file or folder",
+ "errors_missingTitle": "Please enter a title",
+ "errors_missingType": "Please choose a type",
+ "errors_pictureResizeBiggerThanOrg": "You're about to make the picture larger than the original size. Are you sure that you want to proceed?",
+ "errors_pythonErrorHeader": "Error in python script",
+ "errors_pythonErrorText": "The python script has not been saved, because it contained error(s)",
+ "errors_startNodeDoesNotExists": "Startnode deleted, please contact your administrator",
+ "errors_stylesMustMarkBeforeSelect": "Please mark content before changing style",
+ "errors_stylesNoStylesOnPage": "No active styles available",
+ "errors_tableColMergeLeft": "Please place cursor at the left of the two cells you wish to merge",
+ "errors_tableSplitNotSplittable": "You cannot split a cell that hasn't been merged.",
+ "errors_xsltErrorHeader": "Error in XSLT source",
+ "errors_xsltErrorText": "The XSLT has not been saved, because it contained error(s)",
+ "general_about": "About",
+ "general_action": "Action",
+ "general_add": "Add",
+ "general_alias": "Alias",
+ "general_areyousure": "Are you sure?",
+ "general_border": "Border",
+ "general_by": "or",
+ "general_cancel": "Cancel",
+ "general_cellMargin": "Cell margin",
+ "general_choose": "Choose",
+ "general_close": "Close",
+ "general_closewindow": "Close Window",
+ "general_comment": "Comment",
+ "general_confirm": "Confirm",
+ "general_constrainProportions": "Constrain proportions",
+ "general_continue": "Continue",
+ "general_copy": "Copy",
+ "general_create": "Create",
+ "general_database": "Database",
+ "general_date": "Date",
+ "general_default": "Default",
+ "general_delete": "Delete",
+ "general_deleted": "Deleted",
+ "general_deleting": "Deleting...",
+ "general_design": "Design",
+ "general_dimensions": "Dimensions",
+ "general_down": "Down",
+ "general_download": "Download",
+ "general_edit": "Edit",
+ "general_edited": "Edited",
+ "general_elements": "Elements",
+ "general_email": "Email",
+ "general_error": "Error",
+ "general_findDocument": "Find",
+ "general_height": "Height",
+ "general_help": "Help",
+ "general_icon": "Icon",
+ "general_import": "Import",
+ "general_innerMargin": "Inner margin",
+ "general_insert": "Insert",
+ "general_install": "Install",
+ "general_justify": "Justify",
+ "general_language": "Language",
+ "general_layout": "Layout",
+ "general_loading": "Loading",
+ "general_locked": "Locked",
+ "general_login": "Login",
+ "general_logoff": "Log off",
+ "general_logout": "Logout",
+ "general_macro": "Macro",
+ "general_move": "Move",
+ "general_name": "Name",
+ "general_new": "New",
+ "general_next": "Next",
+ "general_no": "No",
+ "general_of": "of",
+ "general_ok": "OK",
+ "general_open": "Open",
+ "general_or": "or",
+ "general_password": "Password",
+ "general_path": "Path",
+ "general_placeHolderID": "Placeholder ID",
+ "general_pleasewait": "One moment please...",
+ "general_previous": "Previous",
+ "general_properties": "Properties",
+ "general_reciept": "Email to receive form data",
+ "general_recycleBin": "Recycle Bin",
+ "general_remaining": "Remaining",
+ "general_rename": "Rename",
+ "general_renew": "Renew",
+ "general_required": "Required",
+ "general_retry": "Retry",
+ "general_rights": "Permissions",
+ "general_search": "Search",
+ "general_server": "Server",
+ "general_show": "Show",
+ "general_showPageOnSend": "Show page on Send",
+ "general_size": "Size",
+ "general_sort": "Sort",
+ "general_type": "Type",
+ "general_typeToSearch": "Type to search...",
+ "general_up": "Up",
+ "general_update": "Update",
+ "general_upgrade": "Upgrade",
+ "general_upload": "Upload",
+ "general_url": "Url",
+ "general_user": "User",
+ "general_username": "Username",
+ "general_value": "Value",
+ "general_view": "View",
+ "general_welcome": "Welcome...",
+ "general_width": "Width",
+ "general_yes": "Yes",
+ "general_folder": "Folder",
+ "general_searchResults": "Search results",
+ "graphicheadline_backgroundcolor": "Background color",
+ "graphicheadline_bold": "Bold",
+ "graphicheadline_color": "Text color",
+ "graphicheadline_font": "Font",
+ "graphicheadline_text": "Text",
+ "headers_page": "Page",
+ "installer_databaseErrorCannotConnect": "The installer cannot connect to the database.",
+ "installer_databaseErrorWebConfig": "Could not save the web.config file. Please modify the connection string manually.",
+ "installer_databaseFound": "Your database has been found and is identified as",
+ "installer_databaseHeader": "Database configuration",
+ "installer_databaseInstall": " Press the install button to install the Umbraco %0% database ",
+ "installer_databaseInstallDone": "Umbraco %0% has now been copied to your database. Press Next to proceed.",
+ "installer_databaseNotFound": "
Database not found! Please check that the information in the 'connection string' of the \"web.config\" file is correct.
To proceed, please edit the 'web.config' file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named 'UmbracoDbDSN' and save the file.
",
+ "installer_databaseText": "To complete this step, you must know some information regarding your database server ('connection string'). Please contact your ISP if necessary. If you're installing on a local machine or server you might need information from your system administrator.",
+ "installer_databaseUpgrade": "
Press the upgrade button to upgrade your database to Umbraco %0%
Don't worry - no content will be deleted and everything will continue working afterwards!
",
+ "installer_databaseUpgradeDone": "Your database has been upgraded to the final version %0%. Press Next to proceed. ",
+ "installer_databaseUpToDate": "Your current database is up-to-date!. Click next to continue the configuration wizard",
+ "installer_defaultUserChangePass": "The Default users' password needs to be changed!",
+ "installer_defaultUserDisabled": "The Default user has been disabled or has no access to Umbraco!
No further actions needs to be taken. Click Next to proceed.",
+ "installer_defaultUserPassChanged": "The Default user's password has been successfully changed since the installation!
No further actions needs to be taken. Click Next to proceed.",
+ "installer_defaultUserPasswordChanged": "The password is changed!",
+ "installer_defaultUserText": "
Umbraco creates a default user with a login ('admin') and password ('default'). It's important that the password is changed to something unique.
This step will check the default user's password and suggest if it needs to be changed.
",
+ "installer_greatStart": "Get a great start, watch our introduction videos",
+ "installer_licenseText": "By clicking the next button (or modifying the UmbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI.",
+ "installer_None": "Not installed yet.",
+ "installer_permissionsAffectedFolders": "Affected files and folders",
+ "installer_permissionsAffectedFoldersMoreInfo": "More information on setting up permissions for Umbraco here",
+ "installer_permissionsAffectedFoldersText": "You need to grant ASP.NET modify permissions to the following files/folders",
+ "installer_permissionsAlmostPerfect": "Your permission settings are almost perfect!
You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.",
+ "installer_permissionsHowtoResolve": "How to Resolve",
+ "installer_permissionsHowtoResolveLink": "Click here to read the text version",
+ "installer_permissionsHowtoResolveText": "Watch our video tutorial on setting up folder permissions for Umbraco or read the text version.",
+ "installer_permissionsMaybeAnIssue": "Your permission settings might be an issue!
You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.",
+ "installer_permissionsNotReady": "Your permission settings are not ready for Umbraco!
In order to run Umbraco, you'll need to update your permission settings.",
+ "installer_permissionsPerfect": "Your permission settings are perfect!
You are ready to run Umbraco and install packages!",
+ "installer_permissionsResolveFolderIssues": "Resolving folder issue",
+ "installer_permissionsResolveFolderIssuesLink": "Follow this link for more information on problems with ASP.NET and creating folders",
+ "installer_permissionsSettingUpPermissions": "Setting up folder permissions",
+ "installer_permissionsText": " Umbraco needs write/modify access to certain directories in order to store files like pictures and PDF's. It also stores temporary data (aka: cache) for enhancing the performance of your website. ",
+ "installer_runwayFromScratch": "I want to start from scratch",
+ "installer_runwayFromScratchText": " Your website is completely empty at the moment, so that's perfect if you want to start from scratch and create your own document types and templates. (learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. ",
+ "installer_runwayHeader": "You've just set up a clean Umbraco platform. What do you want to do next?",
+ "installer_runwayInstalled": "Runway is installed",
+ "installer_runwayInstalledText": " You have the foundation in place. Select what modules you wish to install on top of it. This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules ",
+ "installer_runwayOnlyProUsers": "Only recommended for experienced users",
+ "installer_runwaySimpleSite": "I want to start with a simple website",
+ "installer_runwaySimpleSiteText": "
'Runway' is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, Runway offers an easy foundation based on best practices to get you started faster than ever. If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages.
Included with Runway: Home page, Getting Started page, Installing Modules page. Optional Modules: Top Navigation, Sitemap, Contact, Gallery. ",
+ "installer_runwayWhatIsRunway": "What is Runway",
+ "installer_step1": "Step 1/5 Accept license",
+ "installer_step2": "Step 2/5: Database configuration",
+ "installer_step3": "Step 3/5: Validating File Permissions",
+ "installer_step4": "Step 4/5: Check Umbraco security",
+ "installer_step5": "Step 5/5: Umbraco is ready to get you started",
+ "installer_thankYou": "Thank you for choosing Umbraco",
+ "installer_theEndBrowseSite": "
Browse your new site
You installed Runway, so why not see how your new website looks.",
+ "installer_theEndFurtherHelp": "
Further help and information
Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology",
+ "installer_theEndHeader": "Umbraco %0% is installed and ready for use",
+ "installer_theEndInstallFailed": "To finish the installation, you'll need to manually edit the /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.",
+ "installer_theEndInstallSuccess": "You can get started instantly by clicking the 'Launch Umbraco' button below. If you are new to Umbraco, you can find plenty of resources on our getting started pages.",
+ "installer_theEndOpenUmbraco": "
Launch Umbraco
To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality",
+ "installer_Unavailable": "Connection to database failed.",
+ "installer_Version3": "Umbraco Version 3",
+ "installer_Version4": "Umbraco Version 4",
+ "installer_watch": "Watch",
+ "installer_welcomeIntro": "This wizard will guide you through the process of configuring Umbraco %0% for a fresh install or upgrading from version 3.0.
Press 'next' to start the wizard.",
+ "language_cultureCode": "Culture Code",
+ "language_displayName": "Culture Name",
+ "lockout_lockoutWillOccur": "You've been idle and logout will automatically occur in",
+ "lockout_renewSession": "Renew now to save your work",
+ "login_greeting1": "Happy super Sunday",
+ "login_greeting2": "Happy manic Monday ",
+ "login_greeting3": "Happy tremendous Tuesday",
+ "login_greeting4": "Happy wonderful Wednesday",
+ "login_greeting5": "Happy thunderous Thursday",
+ "login_greeting6": "Happy friendly Friday",
+ "login_greeting7": "Happy shiny Saturday",
+ "login_instruction": "Log in below:",
+ "login_bottomText": "
",
+ "main_dashboard": "Dashboard",
+ "main_sections": "Sections",
+ "main_tree": "Content",
+ "moveOrCopy_choose": "Choose page above...",
+ "moveOrCopy_copyDone": "%0% has been copied to %1%",
+ "moveOrCopy_copyTo": "Select where the document %0% should be copied to below",
+ "moveOrCopy_moveDone": "%0% has been moved to %1%",
+ "moveOrCopy_moveTo": "Select where the document %0% should be moved to below",
+ "moveOrCopy_nodeSelected": "has been selected as the root of your new content, click 'ok' below.",
+ "moveOrCopy_noNodeSelected": "No node selected yet, please select a node in the list above before clicking 'ok'",
+ "moveOrCopy_notAllowedByContentType": "The current node is not allowed under the chosen node because of its type",
+ "moveOrCopy_notAllowedByPath": "The current node cannot be moved to one of its subpages",
+ "moveOrCopy_notAllowedAtRoot": "The current node cannot exist at the root",
+ "moveOrCopy_notValid": "The action isn't allowed since you have insufficient permissions on 1 or more child documents.",
+ "moveOrCopy_relateToOriginal": "Relate copied items to original",
+ "notifications_editNotifications": "Edit your notification for %0%",
+ "notifications_mailBody": " Hi %0% This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' Go to http://%4%/actions/editContent.aspx?id=%5% to edit. Have a nice day! Cheers from the Umbraco robot ",
+ "notifications_mailBodyHtml": "
Hi %0%
This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%'
",
+ "notifications_mailSubject": "[%0%] Notification about %1% performed on %2%",
+ "notifications_notifications": "Notifications",
+ "packager_chooseLocalPackageText": " Choose Package from your machine, by clicking the Browse button and locating the package. Umbraco packages usually have a '.umb' or '.zip' extension. ",
+ "packager_packageAuthor": "Author",
+ "packager_packageDemonstration": "Demonstration",
+ "packager_packageDocumentation": "Documentation",
+ "packager_packageMetaData": "Package meta data",
+ "packager_packageName": "Package name",
+ "packager_packageNoItemsHeader": "Package doesn't contain any items",
+ "packager_packageNoItemsText": "This package file doesn't contain any items to uninstall.
You can safely remove this from the system by clicking 'uninstall package' below.",
+ "packager_packageNoUpgrades": "No upgrades available",
+ "packager_packageOptions": "Package options",
+ "packager_packageReadme": "Package readme",
+ "packager_packageRepository": "Package repository",
+ "packager_packageUninstallConfirm": "Confirm uninstall",
+ "packager_packageUninstalledHeader": "Package was uninstalled",
+ "packager_packageUninstalledText": "The package was successfully uninstalled",
+ "packager_packageUninstallHeader": "Uninstall package",
+ "packager_packageUninstallText": "You can unselect items you do not wish to remove, at this time, below. When you click 'confirm uninstall' all checked-off items will be removed. Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, so uninstall with caution. If in doubt, contact the package author.",
+ "packager_packageUpgradeDownload": "Download update from the repository",
+ "packager_packageUpgradeHeader": "Upgrade package",
+ "packager_packageUpgradeInstructions": "Upgrade instructions",
+ "packager_packageUpgradeText": " There's an upgrade available for this package. You can download it directly from the Umbraco package repository.",
+ "packager_packageVersion": "Package version",
+ "packager_packageVersionHistory": "Package version history",
+ "packager_viewPackageWebsite": "View package website",
+ "paste_doNothing": "Paste with full formatting (Not recommended)",
+ "paste_errorMessage": "The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.",
+ "paste_removeAll": "Paste as raw text without any formatting at all",
+ "paste_removeSpecialFormattering": "Paste, but remove formatting (Recommended)",
+ "publicAccess_paAdvanced": "Role based protection",
+ "publicAccess_paAdvancedHelp": "If you wish to control access to the page using role-based authentication, using Umbraco's member groups.",
+ "publicAccess_paAdvancedNoGroups": "You need to create a membergroup before you can use role-based authentication.",
+ "publicAccess_paErrorPage": "Error Page",
+ "publicAccess_paErrorPageHelp": "Used when people are logged on, but do not have access",
+ "publicAccess_paHowWould": "Choose how to restict access to this page",
+ "publicAccess_paIsProtected": "%0% is now protected",
+ "publicAccess_paIsRemoved": "Protection removed from %0%",
+ "publicAccess_paLoginPage": "Login Page",
+ "publicAccess_paLoginPageHelp": "Choose the page that has the login formular",
+ "publicAccess_paRemoveProtection": "Remove Protection",
+ "publicAccess_paSelectPages": "Select the pages that contain login form and error messages",
+ "publicAccess_paSelectRoles": "Pick the roles who have access to this page",
+ "publicAccess_paSetLogin": "Set the login and password for this page",
+ "publicAccess_paSimple": "Single user protection",
+ "publicAccess_paSimpleHelp": "If you just want to setup simple protection using a single login and password",
+ "publish_contentPublishedFailedInvalid": " %0% could not be published because these properties: %1% did not pass validation rules. ",
+ "publish_contentPublishedFailedByEvent": " %0% could not be published, due to a 3rd party extension cancelling the action. ",
+ "publish_contentPublishedFailedByParent": " %0% can not be published, because a parent page is not published. ",
+ "publish_includeUnpublished": "Include unpublished child pages",
+ "publish_inProgress": "Publishing in progress - please wait...",
+ "publish_inProgressCounter": "%0% out of %1% pages have been published...",
+ "publish_nodePublish": "%0% has been published",
+ "publish_nodePublishAll": "%0% and subpages have been published",
+ "publish_publishAll": "Publish %0% and all its subpages",
+ "publish_publishHelp": "Click ok to publish %0% and thereby making it's content publicly available.
You can publish this page and all it's sub-pages by checking publish all children below. ",
+ "relatedlinks_addExternal": "Add external link",
+ "relatedlinks_addInternal": "Add internal link",
+ "relatedlinks_addlink": "Add",
+ "relatedlinks_caption": "Caption",
+ "relatedlinks_internalPage": "Internal page",
+ "relatedlinks_linkurl": "URL",
+ "relatedlinks_modeDown": "Move Down",
+ "relatedlinks_modeUp": "Move Up",
+ "relatedlinks_newWindow": "Open in new window",
+ "relatedlinks_removeLink": "Remove link",
+ "rollback_currentVersion": "Current version",
+ "rollback_diffHelp": "This shows the differences between the current version and the selected version Red text will not be shown in the selected version. , green means added",
+ "rollback_documentRolledBack": "Document has been rolled back",
+ "rollback_htmlHelp": "This displays the selected version as html, if you wish to see the difference between 2 versions at the same time, use the diff view",
+ "rollback_rollbackTo": "Rollback to",
+ "rollback_selectVersion": "Select version",
+ "rollback_view": "View",
+ "scripts_editscript": "Edit script file",
+ "sections_concierge": "Concierge",
+ "sections_content": "Content",
+ "sections_courier": "Courier",
+ "sections_developer": "Developer",
+ "sections_installer": "Umbraco Configuration Wizard",
+ "sections_media": "Media",
+ "sections_member": "Members",
+ "sections_newsletters": "Newsletters",
+ "sections_settings": "Settings",
+ "sections_statistics": "Statistics",
+ "sections_translation": "Translation",
+ "sections_users": "Users",
+ "sections_contour": "Umbraco Contour",
+ "sections_help": "Help",
+ "settings_defaulttemplate": "Default template",
+ "settings_dictionary editor egenskab": "Dictionary Key",
+ "settings_importDocumentTypeHelp": "To import a document type, find the '.udt' file on your computer by clicking the 'Browse' button and click 'Import' (you'll be asked for confirmation on the next screen)",
+ "settings_newtabname": "New Tab Title",
+ "settings_nodetype": "Nodetype",
+ "settings_objecttype": "Type",
+ "settings_stylesheet": "Stylesheet",
+ "settings_stylesheet editor egenskab": "Stylesheet property",
+ "settings_tab": "Tab",
+ "settings_tabname": "Tab Title",
+ "settings_tabs": "Tabs",
+ "settings_contentTypeEnabled": "Master Content Type enabled",
+ "settings_contentTypeUses": "This Content Type uses",
+ "settings_asAContentMasterType": "as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself",
+ "settings_noPropertiesDefinedOnTab": "No properties defined on this tab. Click on the 'add a new property' link at the top to create a new property.",
+ "sort_sortDone": "Sorting complete.",
+ "sort_sortHelp": "Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items",
+ "sort_sortPleaseWait": " Please wait. Items are being sorted, this can take a while.
Do not close this window during sorting",
+ "speechBubbles_contentPublishedFailedByEvent": "Publishing was cancelled by a 3rd party add-in",
+ "speechBubbles_contentTypeDublicatePropertyType": "Property type already exists",
+ "speechBubbles_contentTypePropertyTypeCreated": "Property type created",
+ "speechBubbles_contentTypePropertyTypeCreatedText": "Name: %0% DataType: %1%",
+ "speechBubbles_contentTypePropertyTypeDeleted": "Propertytype deleted",
+ "speechBubbles_contentTypeSavedHeader": "Document Type saved",
+ "speechBubbles_contentTypeTabCreated": "Tab created",
+ "speechBubbles_contentTypeTabDeleted": "Tab deleted",
+ "speechBubbles_contentTypeTabDeletedText": "Tab with id: %0% deleted",
+ "speechBubbles_cssErrorHeader": "Stylesheet not saved",
+ "speechBubbles_cssSavedHeader": "Stylesheet saved",
+ "speechBubbles_cssSavedText": "Stylesheet saved without any errors",
+ "speechBubbles_dataTypeSaved": "Datatype saved",
+ "speechBubbles_dictionaryItemSaved": "Dictionary item saved",
+ "speechBubbles_editContentPublishedFailedByParent": "Publishing failed because the parent page isn't published",
+ "speechBubbles_editContentPublishedHeader": "Content published",
+ "speechBubbles_editContentPublishedText": "and visible at the website",
+ "speechBubbles_editContentSavedHeader": "Content saved",
+ "speechBubbles_editContentSavedText": "Remember to publish to make changes visible",
+ "speechBubbles_editContentSendToPublish": "Sent For Approval",
+ "speechBubbles_editContentSendToPublishText": "Changes have been sent for approval",
+ "speechBubbles_editMediaSaved": "Media saved",
+ "speechBubbles_editMediaSavedText": "Media saved without any errors",
+ "speechBubbles_editMemberSaved": "Member saved",
+ "speechBubbles_editStylesheetPropertySaved": "Stylesheet Property Saved",
+ "speechBubbles_editStylesheetSaved": "Stylesheet saved",
+ "speechBubbles_editTemplateSaved": "Template saved",
+ "speechBubbles_editUserError": "Error saving user (check log)",
+ "speechBubbles_editUserSaved": "User Saved",
+ "speechBubbles_editUserTypeSaved": "User type saved",
+ "speechBubbles_fileErrorHeader": "File not saved",
+ "speechBubbles_fileErrorText": "file could not be saved. Please check file permissions",
+ "speechBubbles_fileSavedHeader": "File saved",
+ "speechBubbles_fileSavedText": "File saved without any errors",
+ "speechBubbles_languageSaved": "Language saved",
+ "speechBubbles_pythonErrorHeader": "Python script not saved",
+ "speechBubbles_pythonErrorText": "Python script could not be saved due to error",
+ "speechBubbles_pythonSavedHeader": "Python script saved",
+ "speechBubbles_pythonSavedText": "No errors in python script",
+ "speechBubbles_templateErrorHeader": "Template not saved",
+ "speechBubbles_templateErrorText": "Please make sure that you do not have 2 templates with the same alias",
+ "speechBubbles_templateSavedHeader": "Template saved",
+ "speechBubbles_templateSavedText": "Template saved without any errors!",
+ "speechBubbles_xsltErrorHeader": "XSLT not saved",
+ "speechBubbles_xsltErrorText": "XSLT contained an error",
+ "speechBubbles_xsltPermissionErrorText": "XSLT could not be saved, check file permissions",
+ "speechBubbles_xsltSavedHeader": "XSLT saved",
+ "speechBubbles_xsltSavedText": "No errors in XSLT",
+ "speechBubbles_contentUnpublished": "Content unpublished",
+ "speechBubbles_partialViewSavedHeader": "Partial view saved",
+ "speechBubbles_partialViewSavedText": "Partial view saved without any errors!",
+ "speechBubbles_partialViewErrorHeader": "Partial view not saved",
+ "speechBubbles_partialViewErrorText": "An error occurred saving the file.",
+ "stylesheet_aliasHelp": "Uses CSS syntax ex: h1, .redHeader, .blueTex",
+ "stylesheet_editstylesheet": "Edit stylesheet",
+ "stylesheet_editstylesheetproperty": "Edit stylesheet property",
+ "stylesheet_nameHelp": "Name to identify the style property in the rich text editor ",
+ "stylesheet_preview": "Preview",
+ "stylesheet_styles": "Styles",
+ "template_edittemplate": "Edit template",
+ "template_insertContentArea": "Insert content area",
+ "template_insertContentAreaPlaceHolder": "Insert content area placeholder",
+ "template_insertDictionaryItem": "Insert dictionary item",
+ "template_insertMacro": "Insert Macro",
+ "template_insertPageField": "Insert Umbraco page field",
+ "template_mastertemplate": "Master template",
+ "template_quickGuide": "Quick Guide to Umbraco template tags",
+ "template_template": "Template",
+ "templateEditor_alternativeField": "Alternative field",
+ "templateEditor_alternativeText": "Alternative Text",
+ "templateEditor_casing": "Casing",
+ "templateEditor_encoding": "Encoding",
+ "templateEditor_chooseField": "Choose field",
+ "templateEditor_convertLineBreaks": "Convert Linebreaks",
+ "templateEditor_convertLineBreaksHelp": "Replaces linebreaks with html-tag <br>",
+ "templateEditor_customFields": "Custom Fields",
+ "templateEditor_dateOnly": "Yes, Date only",
+ "templateEditor_formatAsDate": "Format as date",
+ "templateEditor_htmlEncode": "HTML encode",
+ "templateEditor_htmlEncodeHelp": "Will replace special characters by their HTML equivalent.",
+ "templateEditor_insertedAfter": "Will be inserted after the field value",
+ "templateEditor_insertedBefore": "Will be inserted before the field value",
+ "templateEditor_lowercase": "Lowercase",
+ "templateEditor_none": "None",
+ "templateEditor_postContent": "Insert after field",
+ "templateEditor_preContent": "Insert before field",
+ "templateEditor_recursive": "Recursive",
+ "templateEditor_removeParagraph": "Remove Paragraph tags",
+ "templateEditor_removeParagraphHelp": "Will remove any <P> in the beginning and end of the text",
+ "templateEditor_standardFields": "Standard Fields",
+ "templateEditor_uppercase": "Uppercase",
+ "templateEditor_urlEncode": "URL encode",
+ "templateEditor_urlEncodeHelp": "Will format special characters in URLs",
+ "templateEditor_usedIfAllEmpty": "Will only be used when the field values above are empty",
+ "templateEditor_usedIfEmpty": "This field will only be used if the primary field is empty",
+ "templateEditor_withTime": "Yes, with time. Seperator: ",
+ "translation_assignedTasks": "Tasks assigned to you",
+ "translation_assignedTasksHelp": " The list below shows translation tasks assigned to you. To see a detailed view including comments, click on 'Details' or just the page name. You can also download the page as XML directly by clicking the 'Download Xml' link. To close a translation task, please go to the Details view and click the 'Close' button. ",
+ "translation_closeTask": "close task",
+ "translation_details": "Translation details",
+ "translation_downloadAllAsXml": "Download all translation tasks as xml",
+ "translation_downloadTaskAsXml": "Download xml",
+ "translation_DownloadXmlDTD": "Download xml DTD",
+ "translation_fields": "Fields",
+ "translation_includeSubpages": "Include subpages",
+ "translation_mailBody": " Hi %0% This is an automated mail to inform you that the document '%1%' has been requested for translation into '%5%' by %2%. Go to http://%3%/translation/details.aspx?id=%4% to edit. Or log into Umbraco to get an overview of your translation tasks http://%3% Have a nice day! Cheers from the Umbraco robot ",
+ "translation_mailSubject": "[%0%] Translation task for %1%",
+ "translation_noTranslators": "No translator users found. Please create a translator user before you start sending content to translation",
+ "translation_ownedTasks": "Tasks created by you",
+ "translation_ownedTasksHelp": " The list below shows pages created by you. To see a detailed view including comments, click on 'Details' or just the page name. You can also download the page as XML directly by clicking the 'Download Xml' link. To close a translation task, please go to the Details view and click the 'Close' button. ",
+ "translation_pageHasBeenSendToTranslation": "The page '%0%' has been send to translation",
+ "translation_sendToTranslate": "Send the page '%0%' to translation",
+ "translation_taskAssignedBy": "Assigned by",
+ "translation_taskOpened": "Task opened",
+ "translation_totalWords": "Total words",
+ "translation_translateTo": "Translate to",
+ "translation_translationDone": "Translation completed.",
+ "translation_translationDoneHelp": "You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages.",
+ "translation_translationFailed": "Translation failed, the xml file might be corrupt",
+ "translation_translationOptions": "Translation options",
+ "translation_translator": "Translator",
+ "translation_uploadTranslationXml": "Upload translation xml",
+ "treeHeaders_cacheBrowser": "Cache Browser",
+ "treeHeaders_contentRecycleBin": "Recycle Bin",
+ "treeHeaders_createdPackages": "Created packages",
+ "treeHeaders_datatype": "Data Types",
+ "treeHeaders_dictionary": "Dictionary",
+ "treeHeaders_installedPackages": "Installed packages",
+ "treeHeaders_installSkin": "Install skin",
+ "treeHeaders_installStarterKit": "Install starter kit",
+ "treeHeaders_languages": "Languages",
+ "treeHeaders_localPackage": "Install local package",
+ "treeHeaders_macros": "Macros",
+ "treeHeaders_mediaTypes": "Media Types",
+ "treeHeaders_member": "Members",
+ "treeHeaders_memberGroup": "Member Groups",
+ "treeHeaders_memberRoles": "Roles",
+ "treeHeaders_memberType": "Member Types",
+ "treeHeaders_nodeTypes": "Document Types",
+ "treeHeaders_packager": "Packages",
+ "treeHeaders_packages": "Packages",
+ "treeHeaders_python": "Python Files",
+ "treeHeaders_repositories": "Install from repository",
+ "treeHeaders_runway": "Install Runway",
+ "treeHeaders_runwayModules": "Runway modules",
+ "treeHeaders_scripting": "Scripting Files",
+ "treeHeaders_scripts": "Scripts",
+ "treeHeaders_stylesheets": "Stylesheets",
+ "treeHeaders_templates": "Templates",
+ "treeHeaders_xslt": "XSLT Files",
+ "update_updateAvailable": "New update ready",
+ "update_updateDownloadText": "%0% is ready, click here for download",
+ "update_updateNoServer": "No connection to server",
+ "update_updateNoServerError": "Error checking for update. Please review trace-stack for further information",
+ "user_administrators": "Administrator",
+ "user_categoryField": "Category field",
+ "user_changePassword": "Change Your Password",
+ "user_newPassword": "New password",
+ "user_confirmNewPassword": "Confirm new password",
+ "user_changePasswordDescription": "You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button",
+ "user_contentChannel": "Content Channel",
+ "user_descriptionField": "Description field",
+ "user_disabled": "Disable User",
+ "user_documentType": "Document Type",
+ "user_editors": "Editor",
+ "user_excerptField": "Excerpt field",
+ "user_language": "Language",
+ "user_loginname": "Login",
+ "user_mediastartnode": "Start Node in Media Library",
+ "user_modules": "Sections",
+ "user_noConsole": "Disable Umbraco Access",
+ "user_oldPassword": "Old password",
+ "user_password": "Password",
+ "user_resetPassword": "Reset password",
+ "user_passwordChanged": "Your password has been changed!",
+ "user_passwordConfirm": "Please confirm the new password",
+ "user_passwordEnterNew": "Enter your new password",
+ "user_passwordIsBlank": "Your new password cannot be blank!",
+ "user_passwordCurrent": "Current password",
+ "user_passwordInvalid": "Invalid current password",
+ "user_passwordIsDifferent": "There was a difference between the new password and the confirmed password. Please try again!",
+ "user_passwordMismatch": "The confirmed password doesn't match the new password!",
+ "user_permissionReplaceChildren": "Replace child node permssions",
+ "user_permissionSelectedPages": "You are currently modifying permissions for the pages:",
+ "user_permissionSelectPages": "Select pages to modify their permissions",
+ "user_searchAllChildren": "Search all children",
+ "user_startnode": "Start Node in Content",
+ "user_username": "Username",
+ "user_userPermissions": "User permissions",
+ "user_usertype": "User type",
+ "user_userTypes": "User types",
+ "user_writer": "Writer",
+ "user_yourProfile": "Your profile",
+ "user_yourHistory": "Your recent history",
+ "user_sessionExpires": "Session expires in"
+ }, null];
+ }
+ }
+
+ return {
+ register: function() {
+ $httpBackend
+ .whenGET(mocksUtils.urlRegex('LocalizedText'))
+ .respond(getLanguageResource);
+ }
+ };
+ }]);
+/**
+* @ngdoc service
+* @name umbraco.mocks.mediaHelperService
+* @description A helper object used for dealing with media items
+**/
+function mediaHelper(umbRequestHelper) {
+ return {
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#getImagePropertyValue
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns the file path associated with the media property if there is one
+ *
+ * @param {object} options Options object
+ * @param {object} options.mediaModel The media object to retrieve the image path from
+ * @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image
+ */
+ getMediaPropertyValue: function (options) {
+ return "assets/img/mocks/big-image.jpg";
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#getImagePropertyValue
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns the actual image path associated with the image property if there is one
+ *
+ * @param {object} options Options object
+ * @param {object} options.imageModel The media object to retrieve the image path from
+ */
+ getImagePropertyValue: function (options) {
+ return "assets/img/mocks/big-image.jpg";
+ },
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#getThumbnail
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * formats the display model used to display the content to the model used to save the content
+ *
+ * @param {object} options Options object
+ * @param {object} options.imageModel The media object to retrieve the image path from
+ */
+ getThumbnail: function (options) {
+
+ if (!options || !options.imageModel) {
+ throw "The options objet does not contain the required parameters: imageModel";
+ }
+
+ var imagePropVal = this.getImagePropertyValue(options);
+ if (imagePropVal !== "") {
+ return this.getThumbnailFromPath(imagePropVal);
+ }
+ return "";
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#scaleToMaxSize
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios
+ *
+ * @param {number} maxSize Maximum width & height
+ * @param {number} width Current width
+ * @param {number} height Current height
+ */
+ scaleToMaxSize: function (maxSize, width, height) {
+ var retval = { width: width, height: height };
+
+ var maxWidth = maxSize; // Max width for the image
+ var maxHeight = maxSize; // Max height for the image
+ var ratio = 0; // Used for aspect ratio
+
+ // Check if the current width is larger than the max
+ if (width > maxWidth) {
+ ratio = maxWidth / width; // get ratio for scaling image
+
+ retval.width = maxWidth;
+ retval.height = height * ratio;
+
+ height = height * ratio; // Reset height to match scaled image
+ width = width * ratio; // Reset width to match scaled image
+ }
+
+ // Check if current height is larger than max
+ if (height > maxHeight) {
+ ratio = maxHeight / height; // get ratio for scaling image
+
+ retval.height = maxHeight;
+ retval.width = width * ratio;
+ width = width * ratio; // Reset width to match scaled image
+ }
+
+ return retval;
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#getThumbnailFromPath
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns the path to the thumbnail version of a given media library image path
+ *
+ * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
+ */
+ getThumbnailFromPath: function (imagePath) {
+ return "assets/img/mocks/big-thumb.jpg";
+ },
+
+ /**
+ * @ngdoc function
+ * @name umbraco.services.mediaHelper#detectIfImageByExtension
+ * @methodOf umbraco.services.mediaHelper
+ * @function
+ *
+ * @description
+ * Returns true/false, indicating if the given path has an allowed image extension
+ *
+ * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
+ */
+ detectIfImageByExtension: function (imagePath) {
+ var lowered = imagePath.toLowerCase();
+ var ext = lowered.substr(lowered.lastIndexOf(".") + 1);
+ return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1;
+ }
+ };
+}
+angular.module('umbraco.mocks').factory('mediaHelper', mediaHelper);
+angular.module('umbraco.mocks').
+ factory('utilMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) {
+ 'use strict';
+
+ function getUpdateCheck(status, data, headers) {
+ //check for existence of a cookie so we can do login/logout in the belle app (ignore for tests).
+ if (!mocksUtils.checkAuth()) {
+ return [401, null, null];
+ }
+ else {
+ return [200, null, null];
+ }
+ }
+
+ return {
+ register: function() {
+ $httpBackend
+ .whenGET(mocksUtils.urlRegex('/umbraco/Api/UpdateCheck/GetCheck'))
+ .respond(getUpdateCheck);
+ }
+ };
+ }]);
+
+})();
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/umbracoCheckKeys.js b/WebCms/Umbraco/Js/umbracoCheckKeys.js
new file mode 100644
index 0000000..6ea47ae
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbracoCheckKeys.js
@@ -0,0 +1,102 @@
+var ctrlDown = false;
+var shiftDown = false;
+var keycode = 0
+
+var currentRichTextDocument = null;
+var currentRichTextObject = null;
+
+function umbracoCheckKeysUp(e) {
+ ctrlDown = e.ctrlKey;
+ shiftDown = e.shiftKey;
+}
+
+function umbracoActivateKeys(ctrl, shift, key) {
+ ctrlDown = ctrl;
+ shiftDown = shift;
+ keycode = key
+ return runShortCuts();
+}
+
+function umbracoActivateKeysUp(ctrl, shift, key) {
+ ctrlDown = ctrl;
+ shiftDown = shift;
+ keycode = key;
+}
+
+function umbracoCheckKeys(e) {
+ ctrlDown = e.ctrlKey;
+ shiftDown = e.shiftKey;
+ keycode = e.keyCode;
+
+ return runShortCuts();
+}
+
+function shortcutCheckKeysPressFirefox(e) {
+ if (ctrlDown && keycode == 83)
+ e.preventDefault();
+}
+
+
+function runShortCuts() {
+ if (currentRichTextObject != undefined && currentRichTextObject != null) {
+ if (ctrlDown) {
+ if (!shiftDown && keycode == 9)
+ functionsFrame.tabSwitch(1);
+ else
+ if (shiftDown && keycode == 9) functionsFrame.tabSwitch(-1);
+
+ if (keycode == 83) {doSubmit(); return false;}
+ if (shiftDown && currentRichTextObject) {
+ if (keycode == 70) {functionsFrame.umbracoInsertForm(myAlias); return false;}
+ if (keycode == 76) {functionsFrame.umbracoLink(myAlias); return false;}
+ if (keycode == 77) {functionsFrame.umbracoInsertMacro(myAlias, umbracoPath); return false;}
+ if (keycode == 80) {functionsFrame.umbracoImage(myAlias); return false;}
+ if (keycode == 84) {functionsFrame.umbracoInsertTable(myAlias); return false;}
+ if (keycode == 86) {functionsFrame.umbracoShowStyles(myAlias); return false;}
+ if (keycode == 85) {functionsFrame.document.getElementById('TabView1_tab01layer_publish').click(); return false;}
+ }
+ }
+
+ } else
+ if (isDialog) {
+ if (keycode == 27) {window.close();} // ESC
+ if (keycode == 13 && functionsFrame.submitOnEnter != undefined) {
+ if (!functionsFrame.disableEnterSubmit) {
+ if (functionsFrame.submitOnEnter) {
+ // firefox hack
+ if (window.addEventListener)
+ e.preventDefault();
+ doSubmit();
+ }
+ }
+ }
+ if (ctrlDown) {
+ if (keycode == 83)
+ doSubmit();
+ else if (keycode == 85)
+ document.getElementById('TabView1_tab01layer_publish').click();
+ else if (!shiftDown && keycode == 9) {
+ functionsFrame.tabSwitch(1);
+ return false;
+ }
+ else
+ if (shiftDown && keycode == 9) {
+ functionsFrame.tabSwitch(-1);
+ return false;
+ }
+
+ }
+ }
+
+ return true;
+
+}
+
+if (window.addEventListener) {
+ document.addEventListener('keyup', umbracoCheckKeysUp, false);
+ document.addEventListener('keydown', umbracoCheckKeys, false);
+ document.addEventListener('keypress', shortcutCheckKeysPressFirefox, false);
+} else {
+ document.attachEvent( "onkeyup", umbracoCheckKeysUp);
+ document.attachEvent("onkeydown", umbracoCheckKeys);
+}
diff --git a/WebCms/Umbraco/Js/umbracoUpgradeChecker.js b/WebCms/Umbraco/Js/umbracoUpgradeChecker.js
new file mode 100644
index 0000000..40e5d87
--- /dev/null
+++ b/WebCms/Umbraco/Js/umbracoUpgradeChecker.js
@@ -0,0 +1,15 @@
+function umbracoCheckUpgrade(result) {
+ if (result) {
+ if (result.UpgradeType.toLowerCase() != 'none') {
+ if (UmbSpeechBubble == null) {
+ InitUmbracoSpeechBubble();
+ }
+ var icon = 'info';
+ if (result.UpgradeType.toLowerCase() == 'critical') {
+ icon = 'error';
+ }
+
+ UmbSpeechBubble.ShowMessage(icon, 'Upgrade Available!', '' + result.UpgradeComment + '', true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/xmlRequest.js b/WebCms/Umbraco/Js/xmlRequest.js
new file mode 100644
index 0000000..fb73625
--- /dev/null
+++ b/WebCms/Umbraco/Js/xmlRequest.js
@@ -0,0 +1,77 @@
+var requestRunning = false;
+var xmlHttp = null;
+var xmlHttpDebug = false;
+
+// Inspired by great work of Webfx in xloadtree
+function umbracoStartXmlRequest(scriptUrl, postData, eventFunction) {
+
+ // random hack for ie7
+ day = new Date();
+ z = day.getTime();
+ y = (z - (parseInt(z/1000,10) * 1000))/10;
+ scriptUrl += "&xmlRnd=" + y;
+
+ if (xmlHttpDebug)
+ alert(scriptUrl)
+
+ this.requestRunning = true;
+ this.xmlHttpObject = XmlHttp.create();
+ if (postData != "") {
+ if (document.all) {
+ this.xmlHttpObject.open("POST", scriptUrl, false);
+ this.xmlHttpObject.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ }
+ else {
+ eval(eventFunction);
+ }
+ } else
+ this.xmlHttpObject.open("GET", scriptUrl, true);
+
+
+ this.xmlHttpObject.onreadystatechange = function () {
+ if (xmlHttp.readyState == 4) {
+ // Removed the this from this.requestRunning = false; this was causing a bug in the find search box in cms backend.
+ requestRunning = false;
+ // debug
+ if (xmlHttpDebug)
+ alert(xmlHttp.responseText)
+ eval(eventFunction);
+ xmlHttp = null;
+ }
+ };
+
+ // call in new thread to allow ui to update
+ window.setTimeout(function () {
+ this.xmlHttpObject.send(postData);
+ }, 10);
+
+ xmlHttp = this.xmlHttpObject;
+ return this;
+}
+
+umbracoStartXmlRequest.prototype.ResultText =
+umbracoStartXmlRequest.prototype.ResultText = function () {
+ return this.xmlHttpObject.responseText;
+}
+
+umbracoStartXmlRequest.prototype.ResultXml =
+umbracoStartXmlRequest.prototype.ResultXml = function () {
+ return this.xmlHttpObject.responseXML;
+}
+
+function umbracoXmlRequestResult() {
+ if (!requestRunning)
+ return xmlHttp.responseXML
+}
+
+function umbracoXmlRequestResultTxt() {
+ if (!requestRunning)
+ return xmlHttp.responseText
+}
+
+function xmlReturnRandom() {
+ day = new Date()
+ z = day.getTime()
+ y = (z - (parseInt(z/1000,10) * 1000))/10
+ return y
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Js/xmlextras.js b/WebCms/Umbraco/Js/xmlextras.js
new file mode 100644
index 0000000..7a87a02
--- /dev/null
+++ b/WebCms/Umbraco/Js/xmlextras.js
@@ -0,0 +1,151 @@
+//
+
+
+
+ Logout
+
+
+
+
+
diff --git a/WebCms/Umbraco/Masterpages/default.Master b/WebCms/Umbraco/Masterpages/default.Master
new file mode 100644
index 0000000..ebbc708
--- /dev/null
+++ b/WebCms/Umbraco/Masterpages/default.Master
@@ -0,0 +1,3 @@
+<%@ Master Language="C#" AutoEventWireup="True" CodeBehind="Default.master.cs" Inherits="Umbraco.Web.UI.Umbraco.Masterpages.Default" %>
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Masterpages/umbracoDialog.Master b/WebCms/Umbraco/Masterpages/umbracoDialog.Master
new file mode 100644
index 0000000..5d16b62
--- /dev/null
+++ b/WebCms/Umbraco/Masterpages/umbracoDialog.Master
@@ -0,0 +1,38 @@
+<%@ Master Language="C#" AutoEventWireup="True" CodeBehind="UmbracoDialog.master.cs" Inherits="Umbraco.Web.UI.Umbraco.Masterpages.UmbracoDialog" %>
+
+
+
+
+
+<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>
+<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
+<%@ Register TagPrefix="umbClient" Namespace="Umbraco.Web.UI.Bundles" Assembly="umbraco" %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Masterpages/umbracoPage.Master b/WebCms/Umbraco/Masterpages/umbracoPage.Master
new file mode 100644
index 0000000..3fcc59a
--- /dev/null
+++ b/WebCms/Umbraco/Masterpages/umbracoPage.Master
@@ -0,0 +1,43 @@
+<%@ Master Language="C#" AutoEventWireup="True" CodeBehind="UmbracoPage.master.cs" Inherits="Umbraco.Web.UI.Umbraco.Masterpages.UmbracoPage" %>
+
+
+
+
+
+<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>
+<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
+<%@ Register TagPrefix="umbClient" Namespace="Umbraco.Web.UI.Bundles" Assembly="umbraco" %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Members/EditMemberGroup.aspx b/WebCms/Umbraco/Members/EditMemberGroup.aspx
new file mode 100644
index 0000000..29913c6
--- /dev/null
+++ b/WebCms/Umbraco/Members/EditMemberGroup.aspx
@@ -0,0 +1,22 @@
+<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoPage.Master" CodeBehind="EditMemberGroup.aspx.cs"
+ AutoEventWireup="True" Inherits="umbraco.presentation.members.EditMemberGroup" %>
+
+<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Members/search.aspx b/WebCms/Umbraco/Members/search.aspx
new file mode 100644
index 0000000..b89f3dc
--- /dev/null
+++ b/WebCms/Umbraco/Members/search.aspx
@@ -0,0 +1,13 @@
+<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoPage.Master" CodeBehind="search.aspx.cs" Inherits="umbraco.presentation.members.search" %>
+<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
+<%@ Register Src="~/umbraco/members/MemberSearch.ascx" TagName="MemberSearch" TagPrefix="umb" %>
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml
new file mode 100644
index 0000000..8ba3dce
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml
@@ -0,0 +1,24 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+@*
+ This snippet makes a breadcrumb of parents using an unordered html list.
+
+ How it works:
+ - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back
+ - Finally it outputs the name of the current page (without a link)
+*@
+
+@{ var selection = CurrentPage.Ancestors(); }
+
+@if (selection.Any())
+{
+
+ @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@
+ @foreach (var item in selection.OrderBy("Level"))
+ {
+
+ }
+
+ @* Display the current page as the last item in the list *@
+
@CurrentPage.Name
+
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml
new file mode 100644
index 0000000..d9606d8
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml
@@ -0,0 +1,66 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@using System.Web.Mvc.Html
+@using ClientDependency.Core.Mvc
+@using Umbraco.Web
+@using Umbraco.Web.Controllers
+
+@{
+ var profileModel = Members.GetCurrentMemberProfileModel();
+
+ Html.EnableClientValidation();
+ Html.EnableUnobtrusiveJavaScript();
+ Html.RequiresJs("/umbraco_client/ui/jquery.js");
+ Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.min.js");
+ Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.unobtrusive.min.js");
+
+ var success = TempData["ProfileUpdateSuccess"] != null;
+}
+
+@*NOTE: This RenderJsHere code should be put on your main template page where the rest of your script tags are placed*@
+@Html.RenderJsHere()
+
+@if (Members.IsLoggedIn() && profileModel != null)
+{
+ if (success)
+ {
+ @* This message will show if RedirectOnSucces is set to false (default) *@
+
Profile updated
+ }
+
+ using (Html.BeginUmbracoForm("HandleUpdateProfile"))
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Empty (ForUseWithCustomViews).cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Empty (ForUseWithCustomViews).cshtml
new file mode 100644
index 0000000..8d10a3a
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/Empty (ForUseWithCustomViews).cshtml
@@ -0,0 +1 @@
+@inherits Umbraco.Web.Mvc.UmbracoViewPage
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Empty.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Empty.cshtml
new file mode 100644
index 0000000..dda3a17
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/Empty.cshtml
@@ -0,0 +1 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Gallery.cshtml
new file mode 100644
index 0000000..52d87c5
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/Gallery.cshtml
@@ -0,0 +1,50 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+@*
+ Macro to display a gallery of images from media the media section.
+ Works with either a 'Single Media Picker' or a 'Multiple Media Picker' macro parameter (see below).
+
+ How it works:
+ - Confirm the macro parameter has been passed in with a value
+ - Loop through all the media Id's passed in (might be a single item, might be many)
+ - Display any individual images, as well as any folders of images
+
+ Macro Parameters To Create, for this macro to work:
+ Alias:mediaIds Name:Select folders and/or images Type: Multiple Media Picker
+ Type: (note: you can use a Single Media Picker if that's more appropriate to your needs)
+*@
+
+@{ var mediaIds = Model.MacroParameters["mediaIds"]; }
+@if (mediaIds != null)
+{
+
+ @foreach (var mediaId in mediaIds.ToString().Split(','))
+ {
+ var media = Umbraco.Media(mediaId);
+
+ @* a single image *@
+ if (media.DocumentTypeAlias == "Image")
+ {
+ @Render(media);
+ }
+
+ @* a folder with images under it *@
+ if (media.Children("Image").Any())
+ {
+ foreach (var image in media.Children("Image"))
+ {
+ @Render(image);
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml
new file mode 100644
index 0000000..9d1284b
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml
@@ -0,0 +1,24 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+@*
+ This snippet makes a list of links to the of parents of the current page using an unordered html list.
+
+ How it works:
+ - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back
+ - Finally it outputs the name of the current page (without a link)
+*@
+
+@{ var selection = CurrentPage.Ancestors(); }
+
+@if (selection.Any())
+{
+
+ @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@
+ @foreach (var item in selection.OrderBy("Level"))
+ {
+
+ }
+
+ @* Display the current page as the last item in the list *@
+
@CurrentPage.Name
+
+}
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml
new file mode 100644
index 0000000..ea45c13
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml
@@ -0,0 +1,33 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@*
+ Macro to list all child pages under a specific page in the content tree.
+
+ How it works:
+ - Confirm the startNodeId macro parameter has been passed in with a value
+ - Loop through all child pages
+ - Display a list of link to those pages, sorted by the value of the propertyAlias
+
+ Macro Parameters To Create, for this macro to work:
+ Alias:startNodeId Name:Select starting page Type:Content Picker
+*@
+
+@{ var startNodeId = Model.MacroParameters["startNodeId"]; }
+@if (startNodeId != null)
+{
+ @* Get the starting page *@
+ var startNode = Umbraco.Content(startNodeId);
+ var selection = startNode.Children.Where("Visible");
+
+ if (selection.Any())
+ {
+
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml
new file mode 100644
index 0000000..b989e94
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml
@@ -0,0 +1,26 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@*
+ Macro to list all child pages with a specific property, sorted by the value of that property.
+
+ How it works:
+ - Confirm the propertyAlias macro parameter has been passed in with a value
+ - Loop through all child pages that have the propertyAlias
+ - Display a list of link to those pages, sorted by the value of the propertyAlias
+
+ Macro Parameters To Create, for this macro to work:
+ Alias:propertyAlias Name:Property Alias Type:Textbox
+*@
+
+@{ var propertyAlias = Model.MacroParameters["propertyAlias"]; }
+@if (propertyAlias != null)
+{
+ var selection = CurrentPage.Children.Where("Visible").OrderBy(propertyAlias);
+
+
+}
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml
new file mode 100644
index 0000000..eb9812e
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml
@@ -0,0 +1,25 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@*
+ This snippet shows how simple it is to fetch only children of a certain Document Type using Razor.
+ Be sure to change "DocumentTypeAlias" below to match your needs, such as "TextPage" or "NewsItems".
+ (You can find the alias of your Document Type by editing it in the Settings section)
+*@
+
+@{ var selection = CurrentPage.Children("DocumentTypeAlias").Where("Visible"); }
+@*
+ As an example of more querying, if you have a true/false property with the alias of shouldBeFeatured:
+ var selection= CurrentPage.Children("DocumentTypeAlias").Where("shouldBeFeatured == true").Where("Visible");
+*@
+
+
+@if (selection.Any())
+{
+
+}
+
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml
new file mode 100644
index 0000000..68f4b38
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml
@@ -0,0 +1,61 @@
+@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
+
+@*
+ This snippet creates links for every single page (no matter how deep) below
+ the page currently being viewed by the website visitor, displayed as nested unordered html lists.
+*@
+
+@{ var selection = CurrentPage.Children.Where("Visible"); }
+
+@* Ensure that the Current Page has children *@
+@if (selection.Any())
+{
+ @* Get the first page in the children, where the property umbracoNaviHide is not True *@
+ var naviLevel = CurrentPage.FirstChild().Where("Visible").Level;
+
+ @* Add in level for a CSS hook *@
+
+ @* For each child page where the property umbracoNaviHide is not True *@
+ @foreach (var item in selection)
+ {
+
+ @item.Name
+
+ @* if this child page has any children, where the property umbracoNaviHide is not True *@
+ @if (item.Children.Where("Visible").Any())
+ {
+ @* Call our helper to display the children *@
+ @childPages(item.Children)
+ }
+
+ }
+
+}
+
+
+@helper childPages(dynamic selection)
+{
+ @* Ensure that we have a collection of pages *@
+ if (selection.Any())
+ {
+ @* Get the first page in pages and get the level *@
+ var naviLevel = selection.First().Level;
+
+ @* Add in level for a CSS hook *@
+
+ @foreach (var item in selection.Where("Visible"))
+ {
+
+ @item.Name
+
+ @* if the this page has any children, where the property umbracoNaviHide is not True *@
+ @if (item.Children.Where("Visible").Any())
+ {
+ @* Call our helper to display the children *@
+ @childPages(item.Children)
+ }
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml
new file mode 100644
index 0000000..5d318aa
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml
@@ -0,0 +1,33 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@*
+ Macro to display a series of images from a media folder.
+
+ How it works:
+ - Confirm the macro parameter has been passed in with a value
+ - Loop through all the media Id's passed in (might be a single item, might be many)
+ - Display any individual images, as well as any folders of images
+
+ Macro Parameters To Create, for this macro to work:
+ Alias:mediaId Name:Select folder with images Type:Single Media Picker
+*@
+
+@{ var mediaId = Model.MacroParameters["mediaId"]; }
+@if (mediaId != null)
+{
+ @* Get all the media item associated with the id passed in *@
+ var media = Umbraco.Media(mediaId);
+ var selection = media.Children("Image");
+
+ if (selection.Any())
+ {
+
+ @foreach (var item in selection)
+ {
+
+
+
+ }
+
+ }
+}
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Login.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Login.cshtml
new file mode 100644
index 0000000..5dfdfb3
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/Login.cshtml
@@ -0,0 +1,41 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@using System.Web.Mvc.Html
+@using ClientDependency.Core.Mvc
+@using Umbraco.Web
+@using Umbraco.Web.Models
+@using Umbraco.Web.Controllers
+
+@{
+ var loginModel = new LoginModel();
+
+ Html.EnableClientValidation();
+ Html.EnableUnobtrusiveJavaScript();
+ Html.RequiresJs("/umbraco_client/ui/jquery.js");
+ Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.min.js");
+ Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.unobtrusive.min.js");
+}
+
+@* NOTE: This RenderJsHere code should be put on your main template page where the rest of your script tags are placed *@
+@Html.RenderJsHere()
+
+@using (Html.BeginUmbracoForm("HandleLogin"))
+{
+
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/LoginStatus.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/LoginStatus.cshtml
new file mode 100644
index 0000000..7c7c926
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/LoginStatus.cshtml
@@ -0,0 +1,43 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+@using System.Web.Mvc.Html
+@using ClientDependency.Core.Mvc
+@using Umbraco.Web
+@using Umbraco.Web.Models
+@using Umbraco.Web.Controllers
+
+@{
+ var loginStatusModel = Members.GetCurrentLoginStatus();
+
+ Html.EnableClientValidation();
+ Html.EnableUnobtrusiveJavaScript();
+ Html.RequiresJs("/umbraco_client/ui/jquery.js");
+ Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.min.js");
+ Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.unobtrusive.min.js");
+
+ var logoutModel = new PostRedirectModel();
+
+ @*
+ Here you can specify a redirect URL for after logging out, by default umbraco will simply
+ redirect to the current page. Example to redirect to the home page:
+
+ logoutModel.RedirectUrl = "/";
+ *@
+}
+
+@* NOTE: This RenderJsHere code should be put on your main template page where the rest of your script tags are placed *@
+@Html.RenderJsHere()
+
+@if (loginStatusModel.IsLoggedIn)
+{
+
You are currently logged in as @loginStatusModel.Name
+
+ using (Html.BeginUmbracoForm("HandleLogout"))
+ {
+
+
+ @Html.HiddenFor(m => logoutModel.RedirectUrl)
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml
new file mode 100644
index 0000000..ca79ad5
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml
@@ -0,0 +1,21 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@*
+ This snippet lists the items from a Multinode tree picker, using the pickers default settings.
+ Content Values stored as xml.
+
+ To get it working with any site's data structure, set the selection equal to the property which has the
+ multinode treepicker (so: replace "PropertyWithPicker" with the alias of your property).
+*@
+
+@{ var selection = CurrentPage.PropertyWithPicker.Split(','); }
+
+
+ @foreach (var id in selection)
+ {
+ var item = Umbraco.Content(id);
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Navigation.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Navigation.cshtml
new file mode 100644
index 0000000..19c0199
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/Navigation.cshtml
@@ -0,0 +1,18 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@*
+ This snippet displays a list of links of the pages immediately under the top-most page in the content tree.
+ This is the home page for a standard website.
+ It also highlights the current active page/section in the navigation with the css class "current".
+*@
+
+@{ var selection = CurrentPage.Site().Children.Where("Visible"); }
+
+
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml
new file mode 100644
index 0000000..3b486e6
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml
@@ -0,0 +1,104 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@using System.Web.Mvc.Html
+@using ClientDependency.Core.Mvc
+@using Umbraco.Web
+@using Umbraco.Web.Controllers
+
+@{
+ @*
+ You can specify a custom member type alias in the constructor, the default is 'Member'
+ for example, to use 'Custom Member' you'd use this syntax:
+
+ var registerModel = Members.CreateRegistrationModel("Custom Member");
+ *@
+
+ var registerModel = Members.CreateRegistrationModel();
+
+ @*
+ Configurable here:
+
+ registerModel.RedirectUrl - Optional. What path to redirect to if registration is successful.
+ By default the member will be redirected to the current umbraco page
+ unless this is specified.
+
+ registerModel.UsernameIsEmail - the default is true
+ if you want the username to be different from the email
+ address, set this to true and add a new Username field in
+ the form below
+
+ @Html.LabelFor(m => registerModel.Username)
+ @Html.TextBoxFor(m => registerModel.Username)
+ @Html.ValidationMessageFor(m => registerModel.Username)
+ *@
+
+ Html.EnableClientValidation();
+ Html.EnableUnobtrusiveJavaScript();
+ Html.RequiresJs("/umbraco_client/ui/jquery.js");
+ Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.min.js");
+ Html.RequiresJs("/umbraco_client/Application/JQuery/jquery.validate.unobtrusive.min.js");
+
+ var success = TempData["FormSuccess"] != null;
+}
+
+@*NOTE: This RenderJsHere code should be put on your main template page where the rest of your script tags are placed*@
+@Html.RenderJsHere()
+
+@if (success)
+{
+ @* This message will show if RedirectOnSucces is set to false (default) *@
+
Registration succeeeded.
+}
+else
+{
+ using (Html.BeginUmbracoForm("HandleRegisterMember"))
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml
new file mode 100644
index 0000000..0fd074c
--- /dev/null
+++ b/WebCms/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml
@@ -0,0 +1,42 @@
+@inherits Umbraco.Web.Macros.PartialViewMacroPage
+
+@*
+ This snippet makes a list of links of all visible pages of the site, as nested unordered html lists.
+
+ How it works:
+ - It uses a custom Razor helper called Traverse() to select and display the markup and links.
+*@
+
+@{ var selection = CurrentPage.Site(); }
+
+
+ @* Render the sitemap by passing the root node to the traverse helper, below *@
+ @Traverse(selection)
+
+
+
+@* Helper method to travers through all descendants *@
+@helper Traverse(dynamic node)
+{
+ @* Update the level to reflect how deep you want the sitemap to go *@
+ var maxLevelForSitemap = 4;
+
+ @* Select visible children *@
+ var selection = node.Children.Where("Visible").Where("Level <= " + maxLevelForSitemap);
+
+ @* If any items are returned, render a list *@
+ if (selection.Any())
+ {
+
+ @foreach (var item in selection)
+ {
+
+ @item.Name
+
+ @* Run the traverse helper again for any child pages *@
+ @Traverse(item)
+
+ @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@
+ @foreach (var page in Model.Ancestors().OrderBy("Level"))
+ {
+
+ }
+
+ @* Display the current page as the last item in the list *@
+
@Model.Name
+
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/EmptyTemplate.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/EmptyTemplate.cshtml
new file mode 100644
index 0000000..811986b
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/EmptyTemplate.cshtml
@@ -0,0 +1,15 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@*
+ Model = The current page the macro is executed on
+ @Model.bodyText
+
+ Parameter = collection of parameter values passed from the macro
+ @Paramter.myParam
+
+ Library = utillity library with common methods
+ @Library.NodeById(1233)
+*@
+
+@* The fun starts here *@
+
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/Gallery.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/Gallery.cshtml
new file mode 100644
index 0000000..0fd41fa
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/Gallery.cshtml
@@ -0,0 +1,32 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@*
+ Macro to display a gallery from a media folder. Add the below parameter to the macro
+ and use it to point the macro at a specific media folder to display it's content as
+ a simple list.
+
+ Macro Parameters To Create, for this macro to work:
+ Alias:mediaId Name:Media Folder ID Type:Single Media Picker
+*@
+
+
+@if (Parameter.mediaId != null)
+{
+ @* Get the media folder as a dynamic node *@
+ var mediaFolder = Library.MediaById(Parameter.mediaId);
+
+ if (mediaFolder.Children.Any())
+ {
+
+ @* for each item in children of the selected media folder *@
+ @foreach (var mediaItem in mediaFolder.Children)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/ListAncestorsFromCurrentPage.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/ListAncestorsFromCurrentPage.cshtml
new file mode 100644
index 0000000..7617654
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/ListAncestorsFromCurrentPage.cshtml
@@ -0,0 +1,16 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@* Check the current page has ancestors *@
+@if (Model.Ancestors().Any())
+{
+
+ @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@
+ @foreach (var page in Model.Ancestors().OrderBy("Level"))
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesFromCurrentPage.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesFromCurrentPage.cshtml
new file mode 100644
index 0000000..d917a38
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesFromCurrentPage.cshtml
@@ -0,0 +1,16 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+
+@* Ensure that the Current Page has children, where the property umbracoNaviHide is not True *@
+@if (Model.Children.Where("Visible").Any())
+{
+
+ @* For each child page under the root node, where the property umbracoNaviHide is not True *@
+ @foreach (var childPage in Model.Children.Where("Visible"))
+ {
+
+ @* OrderBy() takes the property to sort by and optionally order desc/asc *@
+ @foreach (var page in Model.Children.Where("Visible").OrderBy("CreateDate desc"))
+ {
+
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesOrderedByProperty.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesOrderedByProperty.cshtml
new file mode 100644
index 0000000..290ba02
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesOrderedByProperty.cshtml
@@ -0,0 +1,20 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@*
+ Macro parameter to be set on the macro
+ Alias:propertyAlias Name:Property Alias Type:Textbox
+*@
+
+@{
+
+ @* Get the property alias we want to filter on from the macro parameter *@
+ var propertyAlias = Parameter.propertyAlias;
+ var selection = Model.Children.Where("Visible").OrderBy(propertyAlias);
+}
+
+
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesWithDoctype.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesWithDoctype.cshtml
new file mode 100644
index 0000000..a8c39b7
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/ListChildPagesWithDoctype.cshtml
@@ -0,0 +1,30 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@*
+ This snippet shows how simple it is to fetch only children of a certain Document Type using Razor. Instead of
+ calling .Children, simply call .AliasOfDocumentType in plural.
+ For instance .Textpages or .NewsArticles (you can find the alias of your Document Type by editing it in the
+ Settings section).
+*@
+
+@{
+ @* Build a query and return the visible items *@
+ var selection= Model.Textpages.Where("Visible");
+
+ @*
+ Example of more querying, if you have a true/false property with the alias of shouldBeFeatured:
+ var selection= Model.Textpages.Where("shouldBeFeatured == true").Where("Visible");
+ *@
+}
+
+@* Determine if there are any nodes in the selection, then render list *@
+@if(selection.Any()){
+
+
+
+}
+
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/ListDescendantsFromCurrentPage.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/ListDescendantsFromCurrentPage.cshtml
new file mode 100644
index 0000000..e22bfe0
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/ListDescendantsFromCurrentPage.cshtml
@@ -0,0 +1,54 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@* Ensure that the Current Page has children, where the property umbracoNaviHide is not True *@
+@if (Model.Children.Where("Visible").Any())
+{
+ @* Get the first page in the children, where the property umbracoNaviHide is not True *@
+ var naviLevel = Model.Children.Where("Visible").First().Level;
+
+ @* Add in level for a CSS hook *@
+
+ @* For each child page under the root node, where the property umbracoNaviHide is not True *@
+ @foreach (var childPage in Model.Children.Where("Visible"))
+ {
+
+ @childPage.Name
+
+ @* if the current page has any children, where the property umbracoNaviHide is not True *@
+ @if (childPage.Children.Where("Visible").Any())
+ {
+ @* Call our helper to display the children *@
+ @childPages(childPage.Children)
+ }
+
+ }
+
+}
+
+
+@helper childPages(dynamic pages)
+ {
+ @* Ensure that we have a collection of pages *@
+ if (pages.Any())
+ {
+ @* Get the first page in pages and get the level *@
+ var naviLevel = pages.First().Level;
+
+ @* Add in level for a CSS hook *@
+
+ @foreach (var page in pages.Where("Visible"))
+ {
+
+ @page.Name
+
+ @* if the current page has any children, where the property umbracoNaviHide is not True *@
+ @if (page.Children.Where("Visible").Any())
+ {
+ @* Call our helper to display the children *@
+ @childPages(page.Children)
+ }
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/ListImagesFromMediaFolder.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/ListImagesFromMediaFolder.cshtml
new file mode 100644
index 0000000..38a1842
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/ListImagesFromMediaFolder.cshtml
@@ -0,0 +1,23 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@*
+ Macro Parameters To Create, for this macro to work:
+ Alias:mediaId Name:Media Folder ID Type:Single Media Picker
+*@
+
+@if (Parameter.mediaId != null)
+{
+ @* Get the media folder as a dynamic node *@
+ var mediaFolder = Library.MediaById(Parameter.mediaId);
+
+ if (mediaFolder.Children.Any())
+ {
+
+ @* for each item in children of the selected media folder *@
+ @foreach (var mediaItem in mediaFolder.Children)
+ {
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/MultinodeTree-picker.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/MultinodeTree-picker.cshtml
new file mode 100644
index 0000000..4084a86
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/MultinodeTree-picker.cshtml
@@ -0,0 +1,24 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@*
+ Macro to list nodes from a Multinode tree picker, using the pickers default settings.
+ Content Values stored as xml.
+
+ To get it working with any site's data structure, simply set the selection equal to the property which has the
+ multinode treepicker.
+*@
+
+@{
+ var selection = Model.PropertyWithPicker;
+}
+
+@* Lists each selected value from the picker as a link *@
+
+ @foreach(var id in selection.Split(',')){
+
+ @*For each link, get the node, and display its name and url*@
+ var node = Library.NodeById(id);
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/Navigation.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/Navigation.cshtml
new file mode 100644
index 0000000..d7c8d9b
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/Navigation.cshtml
@@ -0,0 +1,21 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@*
+ Macro to display child pages below the root page of a standard website.
+ Also highlights the current active page/section in the navigation with
+ the css class "current".
+*@
+
+@{
+ @* Get the root of the website *@
+ var root = Model.AncestorOrSelf(1);
+}
+
+
+ @foreach (var page in root.Children.Where("Visible"))
+ {
+
diff --git a/WebCms/Umbraco/Scripting/templates/cshtml/SiteMap.cshtml b/WebCms/Umbraco/Scripting/templates/cshtml/SiteMap.cshtml
new file mode 100644
index 0000000..6750f6e
--- /dev/null
+++ b/WebCms/Umbraco/Scripting/templates/cshtml/SiteMap.cshtml
@@ -0,0 +1,34 @@
+@inherits umbraco.MacroEngines.DynamicNodeContext
+
+@* Render the sitemap by passing the root node to the traverse helper *@
+
+ @traverse(@Model.AncestorOrSelf())
+
+
+@* Helper method to travers through all descendants *@
+@helper traverse(dynamic node)
+{
+
+ @* If a MaxLevelForSitemap parameter is passed to the macro, otherwise default to 4 levels *@
+ var maxLevelForSitemap = String.IsNullOrEmpty(Parameter.MaxLevelForSitemap) ? 4 : int.Parse(Parameter.MaxLevelForSitemap);
+
+ @* Select visible children *@
+ var items = node.Children.Where("Visible").Where("Level <= " + maxLevelForSitemap);
+
+
+ @* If any items are returned, render a list *@
+ if (items.Any())
+ {
+
+ @foreach (var item in items)
+ {
+
+ @item.Name
+
+ @*Run the traverse helper again *@
+ @traverse(item)
+
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Search/QuickSearch.ascx b/WebCms/Umbraco/Search/QuickSearch.ascx
new file mode 100644
index 0000000..4081913
--- /dev/null
+++ b/WebCms/Umbraco/Search/QuickSearch.ascx
@@ -0,0 +1,19 @@
+<%@ Control Language="c#" AutoEventWireup="True" Codebehind="QuickSearch.ascx.cs" Inherits="Umbraco.Web.UI.Umbraco.Search.QuickSearch" %>
+<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>
+
+
+
+
+
+
+
+ " />
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Search/QuickSearchHandler.ashx b/WebCms/Umbraco/Search/QuickSearchHandler.ashx
new file mode 100644
index 0000000..0410ca6
--- /dev/null
+++ b/WebCms/Umbraco/Search/QuickSearchHandler.ashx
@@ -0,0 +1 @@
+<%@ WebHandler Language="C#" CodeBehind="QuickSearchHandler.ashx.cs" Class="umbraco.presentation.umbraco.Search.QuickSearchHandler" %>
diff --git a/WebCms/Umbraco/Search/quickSearch.js b/WebCms/Umbraco/Search/quickSearch.js
new file mode 100644
index 0000000..ec14297
--- /dev/null
+++ b/WebCms/Umbraco/Search/quickSearch.js
@@ -0,0 +1,107 @@
+(function ($) {
+
+ $.fn.UmbQuickSearch = function (url) {
+
+ var getSearchApp = function () {
+
+ if (UmbClientMgr.mainWindow().location.hash != "") {
+ switch (UmbClientMgr.mainWindow().location.hash.toLowerCase().substring(1).toLowerCase()) {
+ case "media":
+ return "Media";
+ break;
+ case "content":
+ return "Content";
+ break;
+ case "member":
+ return "Member";
+ break;
+ default:
+ return "Content";
+ }
+ }
+ return "Content";
+
+ /* return (UmbClientMgr.mainWindow().location.hash != ""
+ && UmbClientMgr.mainWindow().location.hash.toLowerCase().substring(1)) == "media".toLowerCase()
+ ? "Media"
+ : "Content"; */
+ };
+
+ var acOptions = {
+ minChars: 2,
+ max: 100,
+ cacheLength: 1,
+ dataType: 'json',
+ matchCase: true,
+ matchContains: false,
+ selectFirst: false, // FR: This enabled the search popup to show, otherwise it selects the first item
+ extraParams: {
+ //return the current app, if it's not media, then it's Content as this is the only searches that are supported.
+ app: function () {
+ return getSearchApp();
+ },
+ rnd: function () {
+ return Umbraco.Utils.generateRandom();
+ }
+ },
+ parse: function (data) {
+ var parsed = [];
+ for (var i = 0; i < data.length; i++) {
+ parsed[parsed.length] = {
+ data: data[i],
+ value: data[i].Id,
+ result: data[i].Fields.nodeName
+ };
+ }
+ return parsed;
+ },
+ formatItem: function (item) {
+ return item.Fields.nodeName + " (" + item.Id + ") ";
+ },
+ focus: function (event, ui) {
+ $(ui).attr("title", $(ui).find("span[title]").attr("title"));
+ }
+ };
+
+ $(this)
+ .autocomplete(url, acOptions)
+ .result(function (e, data) {
+
+ var url = "";
+ switch (getSearchApp()) {
+ case "Media":
+ url = "editMedia.aspx";
+ break;
+ case "Content":
+ url = "editContent.aspx";
+ break;
+ case "Member":
+ url = "members/editMember.aspx";
+ break;
+ default:
+ url = "editContent.aspx";
+ }
+ UmbClientMgr.contentFrame().location.href = url + "?id=" + data.Id;
+ $("#umbSearchField").val(UmbClientMgr.uiKeys()["general_typeToSearch"]);
+ right.focus();
+ });
+
+
+ $(this).focus(function () {
+ $(this).val('');
+ });
+
+ $(this).blur(function () {
+ $(this).val(UmbClientMgr.uiKeys()["general_typeToSearch"]);
+ });
+
+ $(this).keyup(function (e) {
+ if (e.keyCode == 13) {
+
+ UmbClientMgr.openModalWindow('dialogs/search.aspx?rndo=' + Umbraco.Utils.generateRandom() + '&search=' + jQuery(this).val() + '&app=' + getSearchApp(), 'Search', true, 620, 470);
+ return false;
+ }
+ });
+ }
+
+})(jQuery);
\ No newline at end of file
diff --git a/WebCms/Umbraco/Settings/DictionaryItemList.aspx b/WebCms/Umbraco/Settings/DictionaryItemList.aspx
new file mode 100644
index 0000000..6c2f529
--- /dev/null
+++ b/WebCms/Umbraco/Settings/DictionaryItemList.aspx
@@ -0,0 +1,16 @@
+<%@ Page Language="C#" AutoEventWireup="true" Codebehind="DictionaryItemList.aspx.cs"
+ Inherits="umbraco.presentation.settings.DictionaryItemList" MasterPageFile="../masterpages/umbracoPage.Master" %>
+<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
+
+
+
+
+
+
+
+
+ <umbraco:Item field="bodyText" runat="server"/>
+
+
+ Fetches a value from the current page.
+
+
+
Insert macro
+
+ <umbraco:Macro macroAlias="MacroAlias" Alias="MacroAlias" runat="server"/>
+
+ Inserts a macro into the template
+
+
Load child template
+
+ <asp:ContentPlaceHolder runat="server" id="<%= alias %>ContentPlaceHolder" />
+
+
+ This is the default placeholder for content stored in a child template using this exact template as it's master template.
+
+
+
Disable Request Validation
+
+ <umbraco:DisableRequestValidation runat="server"/>
+
+ Disable ASP.NET request validation. It's the same as adding a enableEventValidation="false" to a page directive (but this is not possible in Umbraco as all pages use the same ASPX page for all pages)
+
+ <link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://<%=Request.ServerVariables["SERVER_NAME"] %><%= IOHelper.ResolveUrl(SystemDirectories.Umbraco)%>/channels/wlwmanifest.aspx" />
+
+
+ Insert the above two elements to the head element to gain optimal support for
+ using the MetaBlog Apis with 3rd party clients and to enable autodiscovery for Windows
+ Live Writer.
+
+
+ a b c d e f g h i j k l m n o p q r s t u v w x t z
+
+ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+ 1 2 3 4 5 6 7 8 9 0 $ % & (.,;:'\"!?)
+
+
+ Just keep examining every bid quoted for zinc etchings.
+
+ When you have completed the translation. Upload the edited XML file here. The related translation tasks will automaticly be closed when a file is uploaded.
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Users/PermissionsEditor.js b/WebCms/Umbraco/Users/PermissionsEditor.js
new file mode 100644
index 0000000..82f918b
--- /dev/null
+++ b/WebCms/Umbraco/Users/PermissionsEditor.js
@@ -0,0 +1,144 @@
+///
+///
+///
+///
+
+Umbraco.Sys.registerNamespace("Umbraco.Controls");
+
+(function($) {
+ $.fn.PermissionsEditor = function(opts) {
+ return this.each(function() {
+ var conf = $.extend({
+ userId: -1,
+ pPanelSelector: "",
+ replacePChkBoxSelector: ""
+ }, opts);
+ new Umbraco.Controls.PermissionsEditor().init($(this), conf.userId, $(conf.pPanelSelector), conf.replacePChkBoxSelector);
+ });
+ };
+ $.fn.PermissionsEditorAPI = function() {
+ /// exposes the Permissions Editor API for the selected object
+ return $(this).data("PermissionsEditor") == null ? null : $(this).data("PermissionsEditor");
+ }
+ Umbraco.Controls.PermissionsEditor = function() {
+ ///
+ /// A class to perform the AJAX callback methods for the PermissionsEditor
+ ///
+ return {
+ //private members
+ _userID: -1,
+ _loadingContent: '
',
+ _pPanel: null,
+ _tree: null,
+ _selectedNodes: new Array(),
+ _chkReplaceSelector: null,
+
+ init: function(tree, uId, pPanel, chkReplaceSelector) {
+ ///constructor function
+ this._userID = parseInt(uId);
+ this._pPanel = pPanel;
+ this._tree = tree;
+ this._chkReplaceSelector = chkReplaceSelector;
+
+ //store the api object
+ tree.data("PermissionsEditor", this);
+
+ //bind the nodeClicked event
+ var _this = this;
+ var api = this._tree.UmbracoTreeAPI();
+ api.addEventHandler("nodeClicked", function(e, n) { _this.treeNodeChecked(e, n) });
+ },
+
+ //public methods
+ treeNodeChecked: function(e, data) {
+ var vals = "";
+ var nodeId = $(data).attr("id");
+ this._tree.find("li a.checked").each(function() {
+ var cNodeId = $(this).parent("li").attr("id");
+ //if the check box is not the one thats just been checked, add it
+ if (cNodeId != nodeId && parseInt(cNodeId) > 0) {
+ vals += cNodeId + ",";
+ }
+ });
+ this._selectedNodes = vals.split(",");
+ this._selectedNodes.pop(); //remove the last one as it will be empty
+ //add the one that was just checked to the end of the array if
+ if ($(data).children("a").hasClass("checked")) {
+ this._selectedNodes.push(nodeId);
+ }
+ if (this._selectedNodes.length > 0) {
+ this._beginShowNodePermissions(this._selectedNodes.join(","));
+ this._pPanel.show();
+ }
+ else {
+ this._pPanel.hide();
+ }
+ },
+ setReplaceChild: function(doReplace) {
+ alert(doReplace);
+ this._replaceChildren = doReplace;
+ },
+ beginSavePermissions: function() {
+
+ //ensure that there are nodes selected to save permissions against
+ if (this._selectedNodes.length == 0) {
+ alert("No nodes have been selected");
+ return;
+ }
+ else if (!confirm("Permissions will be changed for nodes: " + this._selectedNodes.join(",") + ". Are you sure?")) {
+ return;
+ }
+
+ //get the list of checked permissions
+ var checkedPermissions = "";
+ this._pPanel.find("input:checked").each(function() {
+ checkedPermissions += $(this).val();
+ });
+
+ var replaceChildren = $(this._chkReplaceSelector).is(":checked");
+
+ this._setUpdateProgress();
+ var _this = this;
+ setTimeout(function() { _this._savePermissions(_this._selectedNodes.join(","), checkedPermissions, replaceChildren); }, 10);
+ },
+
+ //private methods
+ _addItemToSelectedCollection: function(chkbox) {
+ if (chkbox.checked)
+ this._selectedNodes.push(chkbox.value);
+ else {
+ var joined = this._selectedNodes.join(',');
+ joined = joined.replace(chkbox.value, '');
+ this._selectedNodes = joined.split(',');
+ this._selectedNodes = ContentTreeControl_ReBuildArray(contentTreeControl_selected);
+ }
+
+ },
+ _beginShowNodePermissions: function(selectedIDs) {
+ this._setUpdateProgress();
+ var _this = this;
+ setTimeout(function() { _this._showNodePermissions(selectedIDs); }, 10);
+ //setTimeout("PermissionsEditor._showNodePermissions('" + selectedIDs + "');", 10);
+ },
+ _showNodePermissions: function(selectedIDs) {
+ var _this = this;
+ umbraco.cms.presentation.user.PermissionsHandler.GetNodePermissions(this._userID, selectedIDs, function(r) { _this._showNodePermissionsCallback(r); });
+ },
+ _showNodePermissionsCallback: function(result) {
+ this._pPanel.html(result);
+ },
+ _savePermissions: function(nodeIDs, selectedPermissions, replaceChildren) {
+ var _this = this;
+ umbraco.cms.presentation.user.PermissionsHandler.SaveNodePermissions(this._userID, nodeIDs, selectedPermissions, replaceChildren, function(r) { _this._savePermissionsHandler(r) });
+ },
+ _savePermissionsHandler: function(result) {
+ if (UmbClientMgr.mainWindow().UmbSpeechBubble != null)
+ UmbClientMgr.mainWindow().UmbSpeechBubble.ShowMessage("save", "Saved", "Permissions Saved");
+ this._pPanel.html(result);
+ },
+ _setUpdateProgress: function() {
+ this._pPanel.html(this._loadingContent);
+ }
+ }
+ }
+})(jQuery);
\ No newline at end of file
diff --git a/WebCms/Umbraco/Users/PermissionsHandler.asmx b/WebCms/Umbraco/Users/PermissionsHandler.asmx
new file mode 100644
index 0000000..6f3a471
--- /dev/null
+++ b/WebCms/Umbraco/Users/PermissionsHandler.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="PermissionsHandler.asmx.cs" Class="umbraco.cms.presentation.user.PermissionsHandler" %>
diff --git a/WebCms/Umbraco/Views/AuthorizeUpgrade.cshtml b/WebCms/Umbraco/Views/AuthorizeUpgrade.cshtml
new file mode 100644
index 0000000..eb09f33
--- /dev/null
+++ b/WebCms/Umbraco/Views/AuthorizeUpgrade.cshtml
@@ -0,0 +1,78 @@
+@using System.Collections
+@using System.Net.Http
+@using System.Web.Mvc.Html
+@using Umbraco.Core
+@using ClientDependency.Core
+@using ClientDependency.Core.Mvc
+@using Microsoft.Owin.Security
+@using Newtonsoft.Json
+@using Newtonsoft.Json.Linq
+@using Umbraco.Core.IO
+@using Umbraco.Web
+@using Umbraco.Web.Editors
+@using umbraco
+@inherits System.Web.Mvc.WebViewPage
+@{
+ Layout = null;
+
+ Html
+ .RequiresCss("assets/css/umbraco.css", "Umbraco")
+ .RequiresCss("lib/bootstrap-social/bootstrap-social.css", "Umbraco")
+ .RequiresCss("lib/font-awesome/css/font-awesome.min.css", "Umbraco");
+}
+
+
+
+
+
+
+
+
+
+ Umbraco
+
+ @Html.RenderCssHere(
+ new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco)),
+ new BasicPath("UmbracoClient", IOHelper.ResolveUrl(SystemDirectories.UmbracoClient)))
+
+ @*Because we're lazy loading angular js, the embedded cloak style will not be loaded initially, but we need it*@
+
+
+
+
+
+
+
+
+
+
+
+ @{
+ var externalLoginUrl = Url.Action("ExternalLogin", "BackOffice", new
+ {
+ area = ViewBag.UmbracoPath,
+ //Custom redirect URL since we don't want to just redirect to the back office since this is for authing upgrades
+ redirectUrl = Url.Action("AuthorizeUpgrade", "BackOffice")
+ });
+ }
+ @Html.BareMinimumServerVariablesScript(Url, externalLoginUrl)
+
+
+
+ @*And finally we can load in our angular app*@
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/Default.cshtml b/WebCms/Umbraco/Views/Default.cshtml
new file mode 100644
index 0000000..6fd0a23
--- /dev/null
+++ b/WebCms/Umbraco/Views/Default.cshtml
@@ -0,0 +1,98 @@
+@using System.Collections
+@using System.Net.Http
+@using System.Web.Mvc.Html
+@using Umbraco.Core
+@using ClientDependency.Core
+@using ClientDependency.Core.Mvc
+@using Microsoft.Owin.Security
+@using Newtonsoft.Json
+@using Newtonsoft.Json.Linq
+@using Umbraco.Core.IO
+@using Umbraco.Web
+@using Umbraco.Web.Editors
+@using umbraco
+
+@inherits System.Web.Mvc.WebViewPage
+
+@{
+ var isDebug = false;
+ if (Request.RawUrl.IndexOf('?') >= 0)
+ {
+ var parsed = HttpUtility.ParseQueryString(Request.RawUrl.Split('?')[1]);
+ var attempt = parsed["umbDebug"].TryConvertTo();
+ if (attempt && attempt.Result)
+ {
+ isDebug = true;
+ }
+ }
+
+ Html
+ .RequiresCss("assets/css/umbraco.css", "Umbraco")
+ .RequiresCss("tree/treeicons.css", "UmbracoClient")
+ .RequiresCss("lib/bootstrap-social/bootstrap-social.css", "Umbraco")
+ .RequiresCss("lib/font-awesome/css/font-awesome.min.css", "Umbraco");
+}
+
+
+
+
+
+
+
+
+
+
+
+
+ Umbraco
+
+ @Html.RenderCssHere(
+ new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco)),
+ new BasicPath("UmbracoClient", IOHelper.ResolveUrl(SystemDirectories.UmbracoClient)))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewBag.UmbracoPath }))
+
+
+
+
+ @*And finally we can load in our angular app*@
+
+
+
+ @if (isDebug)
+ {
+ @Html.RenderProfiler()
+ }
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/common/dashboard.html b/WebCms/Umbraco/Views/common/dashboard.html
new file mode 100644
index 0000000..ff3315e
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dashboard.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/common/dialogs/approvedcolorpicker.html b/WebCms/Umbraco/Views/common/dialogs/approvedcolorpicker.html
new file mode 100644
index 0000000..91b53f4
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/approvedcolorpicker.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/dialogs/content/edit.html b/WebCms/Umbraco/Views/common/dialogs/content/edit.html
new file mode 100644
index 0000000..55e0cad
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/content/edit.html
@@ -0,0 +1,61 @@
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/dialogs/help.html b/WebCms/Umbraco/Views/common/dialogs/help.html
new file mode 100644
index 0000000..b9428fb
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/help.html
@@ -0,0 +1,58 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/dialogs/iconpicker.html b/WebCms/Umbraco/Views/common/dialogs/iconpicker.html
new file mode 100644
index 0000000..96ab990
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/iconpicker.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/common/dialogs/insertmacro.html b/WebCms/Umbraco/Views/common/dialogs/insertmacro.html
new file mode 100644
index 0000000..590b569
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/insertmacro.html
@@ -0,0 +1,50 @@
+
diff --git a/WebCms/Umbraco/Views/common/dialogs/legacydelete.html b/WebCms/Umbraco/Views/common/dialogs/legacydelete.html
new file mode 100644
index 0000000..5545dac
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/legacydelete.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Are you sure you want to delete {{currentNode.name}} ?
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/common/dialogs/linkpicker.html b/WebCms/Umbraco/Views/common/dialogs/linkpicker.html
new file mode 100644
index 0000000..a1728f3
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/linkpicker.html
@@ -0,0 +1,76 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/dialogs/login.html b/WebCms/Umbraco/Views/common/dialogs/login.html
new file mode 100644
index 0000000..4e0268e
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/login.html
@@ -0,0 +1,135 @@
+
+
+
+
{{greeting}}
+
+
+
+
+ Log in below.
+ Log in below
+
+
+
+
+
+ {{error}}
+
+
+
+
+
+
+
or
+
+
+
+
+
+
+
+
+
+ An email will be sent to the address specified with a link to reset your password
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/dialogs/macropicker.html b/WebCms/Umbraco/Views/common/dialogs/macropicker.html
new file mode 100644
index 0000000..115109c
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/macropicker.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/dialogs/mediapicker.html b/WebCms/Umbraco/Views/common/dialogs/mediapicker.html
new file mode 100644
index 0000000..e770358
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/mediapicker.html
@@ -0,0 +1,159 @@
+
diff --git a/WebCms/Umbraco/Views/common/dialogs/template/querybuilder.html b/WebCms/Umbraco/Views/common/dialogs/template/querybuilder.html
new file mode 100644
index 0000000..1c0aaf7
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/template/querybuilder.html
@@ -0,0 +1,158 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/dialogs/template/snippet.html b/WebCms/Umbraco/Views/common/dialogs/template/snippet.html
new file mode 100644
index 0000000..7b51873
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/template/snippet.html
@@ -0,0 +1,37 @@
+
diff --git a/WebCms/Umbraco/Views/common/dialogs/treepicker.html b/WebCms/Umbraco/Views/common/dialogs/treepicker.html
new file mode 100644
index 0000000..c3569c6
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/treepicker.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/common/dialogs/user.html b/WebCms/Umbraco/Views/common/dialogs/user.html
new file mode 100644
index 0000000..20dc41d
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/user.html
@@ -0,0 +1,119 @@
+
diff --git a/WebCms/Umbraco/Views/common/dialogs/ysod.html b/WebCms/Umbraco/Views/common/dialogs/ysod.html
new file mode 100644
index 0000000..1734867
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/dialogs/ysod.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/legacy.html b/WebCms/Umbraco/Views/common/legacy.html
new file mode 100644
index 0000000..2fd41be
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/legacy.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/login.html b/WebCms/Umbraco/Views/common/login.html
new file mode 100644
index 0000000..d490d39
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/login.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/notifications/confirmroutechange.html b/WebCms/Umbraco/Views/common/notifications/confirmroutechange.html
new file mode 100644
index 0000000..0b29263
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/notifications/confirmroutechange.html
@@ -0,0 +1,7 @@
+
+
You have unsaved changes
+
Are you sure you want to navigate away from this page? - you have unsaved changes
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/overlays/contentpicker/contentpicker.html b/WebCms/Umbraco/Views/common/overlays/contentpicker/contentpicker.html
new file mode 100644
index 0000000..8e70ad1
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/overlays/contentpicker/contentpicker.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/compositions/compositions.html b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/compositions/compositions.html
new file mode 100644
index 0000000..6554a03
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/compositions/compositions.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html
new file mode 100644
index 0000000..392d45e
--- /dev/null
+++ b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html
@@ -0,0 +1,117 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/application/umb-navigation.html b/WebCms/Umbraco/Views/components/application/umb-navigation.html
new file mode 100644
index 0000000..7a64394
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/application/umb-navigation.html
@@ -0,0 +1,104 @@
+
diff --git a/WebCms/Umbraco/Views/components/editor/umb-editor-sub-views.html b/WebCms/Umbraco/Views/components/editor/umb-editor-sub-views.html
new file mode 100644
index 0000000..6327e9f
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/editor/umb-editor-sub-views.html
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/WebCms/Umbraco/Views/components/editor/umb-editor-view.html b/WebCms/Umbraco/Views/components/editor/umb-editor-view.html
new file mode 100644
index 0000000..f99603a
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/editor/umb-editor-view.html
@@ -0,0 +1,7 @@
+
+
+
diff --git a/WebCms/Umbraco/Views/components/html/umb-control-group.html b/WebCms/Umbraco/Views/components/html/umb-control-group.html
new file mode 100644
index 0000000..342c96e
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/html/umb-control-group.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/html/umb-pane.html b/WebCms/Umbraco/Views/components/html/umb-pane.html
new file mode 100644
index 0000000..7c94769
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/html/umb-pane.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/html/umb-panel.html b/WebCms/Umbraco/Views/components/html/umb-panel.html
new file mode 100644
index 0000000..a963eff
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/html/umb-panel.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/WebCms/Umbraco/Views/components/imaging/umb-image-crop.html b/WebCms/Umbraco/Views/components/imaging/umb-image-crop.html
new file mode 100644
index 0000000..4f431de
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/imaging/umb-image-crop.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/imaging/umb-image-gravity.html b/WebCms/Umbraco/Views/components/imaging/umb-image-gravity.html
new file mode 100644
index 0000000..21500e7
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/imaging/umb-image-gravity.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/components/imaging/umb-image-thumbnail.html b/WebCms/Umbraco/Views/components/imaging/umb-image-thumbnail.html
new file mode 100644
index 0000000..2f3861a
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/imaging/umb-image-thumbnail.html
@@ -0,0 +1,5 @@
+
+
+
diff --git a/WebCms/Umbraco/Views/components/notifications/umb-notifications.html b/WebCms/Umbraco/Views/components/notifications/umb-notifications.html
new file mode 100644
index 0000000..d8245ab
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/notifications/umb-notifications.html
@@ -0,0 +1,21 @@
+
diff --git a/WebCms/Umbraco/Views/components/overlays/umb-overlay-backdrop.html b/WebCms/Umbraco/Views/components/overlays/umb-overlay-backdrop.html
new file mode 100644
index 0000000..23ffbf6
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/overlays/umb-overlay-backdrop.html
@@ -0,0 +1 @@
+
{{ numberOfOverlays }}
diff --git a/WebCms/Umbraco/Views/components/overlays/umb-overlay.html b/WebCms/Umbraco/Views/components/overlays/umb-overlay.html
new file mode 100644
index 0000000..4724084
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/overlays/umb-overlay.html
@@ -0,0 +1,76 @@
+
+
+
+
+
{{model.title}}
+
{{model.subtitle}}
+
+
+
+
+
+
+
+
+
+
+
{{ model.itemDetails.title }}
+
+
+
{{ model.itemDetails.description }}
+
+
+
+
+
+
+
+
{{ model.confirmSubmit.title }}
+
{{ model.confirmSubmit.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/components/property/umb-property-editor.html b/WebCms/Umbraco/Views/components/property/umb-property-editor.html
new file mode 100644
index 0000000..bbf70f7
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/property/umb-property-editor.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/WebCms/Umbraco/Views/components/property/umb-property-group.html b/WebCms/Umbraco/Views/components/property/umb-property-group.html
new file mode 100644
index 0000000..e69de29
diff --git a/WebCms/Umbraco/Views/components/property/umb-property.html b/WebCms/Umbraco/Views/components/property/umb-property.html
new file mode 100644
index 0000000..7517795
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/property/umb-property.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/tabs/umb-tab.html b/WebCms/Umbraco/Views/components/tabs/umb-tab.html
new file mode 100644
index 0000000..8ee6fb4
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/tabs/umb-tab.html
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/components/tabs/umb-tabs-content.html b/WebCms/Umbraco/Views/components/tabs/umb-tabs-content.html
new file mode 100644
index 0000000..8e1c7d4
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/tabs/umb-tabs-content.html
@@ -0,0 +1 @@
+
diff --git a/WebCms/Umbraco/Views/components/tabs/umb-tabs-nav.html b/WebCms/Umbraco/Views/components/tabs/umb-tabs-nav.html
new file mode 100644
index 0000000..8522bc3
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/tabs/umb-tabs-nav.html
@@ -0,0 +1,5 @@
+
diff --git a/WebCms/Umbraco/Views/components/tree/umb-tree-search-box.html b/WebCms/Umbraco/Views/components/tree/umb-tree-search-box.html
new file mode 100644
index 0000000..fde38dc
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/tree/umb-tree-search-box.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Search:
+ {{searchFromName}}
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/tree/umb-tree-search-results.html b/WebCms/Umbraco/Views/components/tree/umb-tree-search-results.html
new file mode 100644
index 0000000..97f08fe
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/tree/umb-tree-search-results.html
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/umb-content-grid.html b/WebCms/Umbraco/Views/components/umb-content-grid.html
new file mode 100644
index 0000000..8ac49e4
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/umb-content-grid.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ item.name }}
+
+
+
+
{{ property.header }}:
+
{{ item[property.alias] }}
+
+
+
+
+
+
+
+
+ There are no items to show
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/umb-empty-state.html b/WebCms/Umbraco/Views/components/umb-empty-state.html
new file mode 100644
index 0000000..e982277
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/umb-empty-state.html
@@ -0,0 +1,9 @@
+
+
diff --git a/WebCms/Umbraco/Views/components/umb-folder-grid.html b/WebCms/Umbraco/Views/components/umb-folder-grid.html
new file mode 100644
index 0000000..df5eafb
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/umb-folder-grid.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
{{ folder.name }}
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/components/umb-generate-alias.html b/WebCms/Umbraco/Views/components/umb-generate-alias.html
new file mode 100644
index 0000000..6932747
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/umb-generate-alias.html
@@ -0,0 +1,12 @@
+
+ {{ alias }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/components/umb-grid-selector.html b/WebCms/Umbraco/Views/components/umb-grid-selector.html
new file mode 100644
index 0000000..0fa236f
--- /dev/null
+++ b/WebCms/Umbraco/Views/components/umb-grid-selector.html
@@ -0,0 +1,43 @@
+
diff --git a/WebCms/Umbraco/Views/content/copy.html b/WebCms/Umbraco/Views/content/copy.html
new file mode 100644
index 0000000..1c179cd
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/copy.html
@@ -0,0 +1,77 @@
+
+
+
+
+ Choose where to copy {{currentNode.name}} to in the tree structure below
+
+
+
+
+
+
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+
+
+ {{currentNode.name}} was copied to
+ {{target.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/content/create.html b/WebCms/Umbraco/Views/content/create.html
new file mode 100644
index 0000000..22040ff
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/create.html
@@ -0,0 +1,45 @@
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/content/delete.html b/WebCms/Umbraco/Views/content/delete.html
new file mode 100644
index 0000000..abe655e
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/delete.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Are you sure you want to delete{{currentNode.name}} ?
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/content/edit.html b/WebCms/Umbraco/Views/content/edit.html
new file mode 100644
index 0000000..baf1cd7
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/edit.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/content/emptyrecyclebin.html b/WebCms/Umbraco/Views/content/emptyrecyclebin.html
new file mode 100644
index 0000000..ec3ff44
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/emptyrecyclebin.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+ When items are deleted from the recycle bin, they will be gone forever.
+ Are you sure?
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/content/move.html b/WebCms/Umbraco/Views/content/move.html
new file mode 100644
index 0000000..73dc47b
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/move.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+ Choose where to move {{currentNode.name}} to in the tree structure below
+
+
+
+
+
+
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+
+
{{currentNode.name}} was moved underneath {{target.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/content/recyclebin.html b/WebCms/Umbraco/Views/content/recyclebin.html
new file mode 100644
index 0000000..2a6efa3
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/recyclebin.html
@@ -0,0 +1,16 @@
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/content/restore.html b/WebCms/Umbraco/Views/content/restore.html
new file mode 100644
index 0000000..c1901c4
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/restore.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Restore {{currentNode.name}} to under {{target.name}}?
+
+
+
+
{{error.errorMsg}}
+
{{error.data.Message}}
+
+
+
+
{{currentNode.name}} was moved underneath {{target.name}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/content/umbpreview.html b/WebCms/Umbraco/Views/content/umbpreview.html
new file mode 100644
index 0000000..3cdf145
--- /dev/null
+++ b/WebCms/Umbraco/Views/content/umbpreview.html
@@ -0,0 +1,9 @@
+
+
+
+ Loading
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/dashboard/ChangePassword.html b/WebCms/Umbraco/Views/dashboard/ChangePassword.html
new file mode 100644
index 0000000..7b1398b
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/ChangePassword.html
@@ -0,0 +1,18 @@
+
diff --git a/WebCms/Umbraco/Views/dashboard/default/StartupDashboardIntro.html b/WebCms/Umbraco/Views/dashboard/default/StartupDashboardIntro.html
new file mode 100644
index 0000000..63d5420
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/default/StartupDashboardIntro.html
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Welcome to The Friendly CMS
+
+ Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible.
+
+ Umbraco.TV will help you go from zero to Umbraco
+ hero at a pace that suits you. Our easy to follow
+ online training videos will give you the fundamental
+ knowledge to start building awesome Umbraco websites.
+
+ Our Umbraco - the official community site is your one
+ stop for everything Umbraco. Whether you need a
+ question answered or looking for cool plugins, the
+ worlds best community is just a click away.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/dashboard/default/StartupDashboardKits.html b/WebCms/Umbraco/Views/dashboard/default/StartupDashboardKits.html
new file mode 100644
index 0000000..29049d0
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/default/StartupDashboardKits.html
@@ -0,0 +1,15 @@
+
Install a Starter Site and Skin
+
If you haven't already installed one of our Starter Kits, we think you should do that now. This is one of the best ways to start working with Umbraco. After you install a Starter Kit, you can select a skin to make it look great and customize the kit to your liking.
+
Starter Kits:
+
+
+
+
+
Simple Starter Kit a bare-bones website that introduces you to a set of well-defined conventions for building an Umbraco website
+
Blog Starter Kit a powerful blog kit with all the bells and whistles
diff --git a/WebCms/Umbraco/Views/dashboard/default/StartupDashboardLastEdits.html b/WebCms/Umbraco/Views/dashboard/default/StartupDashboardLastEdits.html
new file mode 100644
index 0000000..e69de29
diff --git a/WebCms/Umbraco/Views/dashboard/default/StartupDashboardVideos.html b/WebCms/Umbraco/Views/dashboard/default/StartupDashboardVideos.html
new file mode 100644
index 0000000..1a83a5e
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/default/StartupDashboardVideos.html
@@ -0,0 +1,18 @@
+
Hours of Umbraco training videos are only a click away
+
Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos
diff --git a/WebCms/Umbraco/Views/dashboard/developer/developerdashboardvideos.html b/WebCms/Umbraco/Views/dashboard/developer/developerdashboardvideos.html
new file mode 100644
index 0000000..bc428fc
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/developer/developerdashboardvideos.html
@@ -0,0 +1,20 @@
+
Hours of Umbraco training videos are only a click away
+
Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos
diff --git a/WebCms/Umbraco/Views/dashboard/developer/healthcheck.html b/WebCms/Umbraco/Views/dashboard/developer/healthcheck.html
new file mode 100644
index 0000000..ca79604
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/developer/healthcheck.html
@@ -0,0 +1,139 @@
+
+
+
+
+
Health Check
+
+
The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button.
+ You can add your own health checks, have a look at the documentation for more information about custom health checks.
+ When a published page gets renamed or moved a redirect will automatically be made to the new page
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/dashboard/developer/xmldataintegrityreport.html b/WebCms/Umbraco/Views/dashboard/developer/xmldataintegrityreport.html
new file mode 100644
index 0000000..11f1834
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/developer/xmldataintegrityreport.html
@@ -0,0 +1,29 @@
+
+
+
Xml Cache Data integrity
+
+
+ Loading...
+
+
+
+ This checks the data integrity for the xml structures for content, media and members that are stored in the cmsContentXml table.
+ This does not check the data integrity of the xml cache file, only the xml structures stored in the database used to create the xml cache file.
+
+
+ {{value.label}} ...
+ Checking...
+ Ok
+ Error
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/dashboard/forms/formsdashboardintro.html b/WebCms/Umbraco/Views/dashboard/forms/formsdashboardintro.html
new file mode 100644
index 0000000..018c520
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/forms/formsdashboardintro.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
Umbraco Forms
+
+
+
+
+
+
+
+
+
Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!
+
+
+
+
+
+
Installing...
+
+
+
+
+ {{state}}
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/dashboard/media/desktopmediauploader.html b/WebCms/Umbraco/Views/dashboard/media/desktopmediauploader.html
new file mode 100644
index 0000000..59a50f4
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/media/desktopmediauploader.html
@@ -0,0 +1,38 @@
+
Desktop Media Uploader
+
+
Desktop Media Uploader is a small desktop application that you can install on your computer which allows you to easily upload media items directly to the media section.
+
The badge below will auto configure itself based upon whether you already have Desktop Media Uploader installed or not.
+
Just click the Install Now / Upgrade Now / Launch Now link to perform that action.
Use the tool below to upload your images or documents to a media folder.
+
+
Follow these steps:
+
+
Click Install and follow the on screen instructions to install the Desktop Media Uploader
+
Enter your login details for the site and click Sign In
+
Choose a media folder to upload files to from the Upload files to... dropdown list
+
Drag the files and folders you wish to upload directly into the Desktop Media Uploader application
+
Click Upload to start uploading
+
+
For a more thorough guide on how to use the Desktop Media Uploader, checkout this video.
diff --git a/WebCms/Umbraco/Views/dashboard/media/mediadashboardvideos.html b/WebCms/Umbraco/Views/dashboard/media/mediadashboardvideos.html
new file mode 100644
index 0000000..2135c37
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/media/mediadashboardvideos.html
@@ -0,0 +1,18 @@
+
Hours of Umbraco training videos are only a click away
+
Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/dashboard/media/mediafolderbrowser.html b/WebCms/Umbraco/Views/dashboard/media/mediafolderbrowser.html
new file mode 100644
index 0000000..d49bdd8
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/media/mediafolderbrowser.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/WebCms/Umbraco/Views/dashboard/members/membersdashboardintro.html b/WebCms/Umbraco/Views/dashboard/members/membersdashboardintro.html
new file mode 100644
index 0000000..d42d3eb
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/members/membersdashboardintro.html
@@ -0,0 +1,10 @@
+
Start here
+
+
Get started with Members right now
+
Use the tool below to search for an existing member.
+
+
More about members
+
+
+
Learn about how to protect pages of your site from this Wiki entry
+
diff --git a/WebCms/Umbraco/Views/dashboard/members/membersdashboardvideos.html b/WebCms/Umbraco/Views/dashboard/members/membersdashboardvideos.html
new file mode 100644
index 0000000..08e7fb3
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/members/membersdashboardvideos.html
@@ -0,0 +1,18 @@
+
Hours of Umbraco training videos are only a click away
+
Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/dashboard/settings/settingsdashboardintro.html b/WebCms/Umbraco/Views/dashboard/settings/settingsdashboardintro.html
new file mode 100644
index 0000000..3a45776
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/settings/settingsdashboardintro.html
@@ -0,0 +1,14 @@
+
Start here
+
This section contains the building blocks for your Umbraco site
+
Follow the below links to find out more about working with the items in the Settings section:
diff --git a/WebCms/Umbraco/Views/dashboard/settings/settingsdashboardvideos.html b/WebCms/Umbraco/Views/dashboard/settings/settingsdashboardvideos.html
new file mode 100644
index 0000000..2135c37
--- /dev/null
+++ b/WebCms/Umbraco/Views/dashboard/settings/settingsdashboardvideos.html
@@ -0,0 +1,18 @@
+
Hours of Umbraco training videos are only a click away
+
Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/directives/_obsolete/umb-header.html b/WebCms/Umbraco/Views/directives/_obsolete/umb-header.html
new file mode 100644
index 0000000..c65b373
--- /dev/null
+++ b/WebCms/Umbraco/Views/directives/_obsolete/umb-header.html
@@ -0,0 +1,14 @@
+
diff --git a/WebCms/Umbraco/Views/directives/_obsolete/umb-tab-view.html b/WebCms/Umbraco/Views/directives/_obsolete/umb-tab-view.html
new file mode 100644
index 0000000..93b520c
--- /dev/null
+++ b/WebCms/Umbraco/Views/directives/_obsolete/umb-tab-view.html
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/directives/_obsolete/umb-upload-dropzone.html b/WebCms/Umbraco/Views/directives/_obsolete/umb-upload-dropzone.html
new file mode 100644
index 0000000..20a8f9d
--- /dev/null
+++ b/WebCms/Umbraco/Views/directives/_obsolete/umb-upload-dropzone.html
@@ -0,0 +1,8 @@
+
+
+
+
+ Drop your files here...
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/documenttypes/copy.html b/WebCms/Umbraco/Views/documenttypes/copy.html
new file mode 100644
index 0000000..db1a0db
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/copy.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+ Select the folder to copy{{currentNode.name}}to in the tree structure below
+
diff --git a/WebCms/Umbraco/Views/documenttypes/create.html b/WebCms/Umbraco/Views/documenttypes/create.html
new file mode 100644
index 0000000..2a99fb7
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/create.html
@@ -0,0 +1,56 @@
+
+
+
diff --git a/WebCms/Umbraco/Views/documenttypes/delete.html b/WebCms/Umbraco/Views/documenttypes/delete.html
new file mode 100644
index 0000000..d0826bf
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/delete.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+ Are you sure you want to delete
+ {{currentNode.name}} ?
+
+
+
+
+
+
+
+
+
+
+
+
+ All Documents
+
+
+ using this document type will be deleted permanently, please confirm you want to delete these as well.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/documenttypes/edit.html b/WebCms/Umbraco/Views/documenttypes/edit.html
new file mode 100644
index 0000000..39efb9d
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/edit.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/documenttypes/move.html b/WebCms/Umbraco/Views/documenttypes/move.html
new file mode 100644
index 0000000..c982abc
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/move.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+ Select the folder to move{{currentNode.name}}to in the tree structure below
+
+
+
+
+
+
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+
+
+ {{currentNode.name}}was moved underneath{{target.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/documenttypes/property.html b/WebCms/Umbraco/Views/documenttypes/property.html
new file mode 100644
index 0000000..ccd6322
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/property.html
@@ -0,0 +1,4 @@
+
+
+ {{property.description}}
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/documenttypes/propertygroup.html b/WebCms/Umbraco/Views/documenttypes/propertygroup.html
new file mode 100644
index 0000000..e69de29
diff --git a/WebCms/Umbraco/Views/documenttypes/views/design/design.html b/WebCms/Umbraco/Views/documenttypes/views/design/design.html
new file mode 100644
index 0000000..805be7b
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/views/design/design.html
@@ -0,0 +1,4 @@
+
+
diff --git a/WebCms/Umbraco/Views/documenttypes/views/listview/listview.html b/WebCms/Umbraco/Views/documenttypes/views/listview/listview.html
new file mode 100644
index 0000000..c27d885
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/views/listview/listview.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/documenttypes/views/permissions/permissions.html b/WebCms/Umbraco/Views/documenttypes/views/permissions/permissions.html
new file mode 100644
index 0000000..d9c7d18
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/views/permissions/permissions.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/documenttypes/views/templates/templates.html b/WebCms/Umbraco/Views/documenttypes/views/templates/templates.html
new file mode 100644
index 0000000..0cfe76c
--- /dev/null
+++ b/WebCms/Umbraco/Views/documenttypes/views/templates/templates.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/install/continueinstall.html b/WebCms/Umbraco/Views/install/continueinstall.html
new file mode 100644
index 0000000..28fa1d3
--- /dev/null
+++ b/WebCms/Umbraco/Views/install/continueinstall.html
@@ -0,0 +1,13 @@
+
+
Continue Umbraco Installation
+
+ You see this screen because your Umbraco installation did not complete correctly.
+
+
+ Simply click continue below to be guided through the rest of the installation process.
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/install/database.html b/WebCms/Umbraco/Views/install/database.html
new file mode 100644
index 0000000..1b13768
--- /dev/null
+++ b/WebCms/Umbraco/Views/install/database.html
@@ -0,0 +1,118 @@
+
+
+
Configure your database
+
+ Enter connection and authentication details for the database you want to install Umbraco on
+
+
+
+
diff --git a/WebCms/Umbraco/Views/install/error.html b/WebCms/Umbraco/Views/install/error.html
new file mode 100644
index 0000000..4fb5823
--- /dev/null
+++ b/WebCms/Umbraco/Views/install/error.html
@@ -0,0 +1,9 @@
+
+
Error during installation
+
+
{{installer.current.model.message}}
+
+
See the log for full details (logs can typically be found in the App_Data\Logs folder).
+
+
+
diff --git a/WebCms/Umbraco/Views/install/permissionsreport.html b/WebCms/Umbraco/Views/install/permissionsreport.html
new file mode 100644
index 0000000..3ced6ed
--- /dev/null
+++ b/WebCms/Umbraco/Views/install/permissionsreport.html
@@ -0,0 +1,26 @@
+
+
Your permission settings are not ready for umbraco
+
+ In order to run umbraco, you'll need to update your permission settings.
+ Detailed information about the correct file & folder permissions for Umbraco can be found
+ here.
+
+
+ The following report list the permissions that are currently failing. Once the permissions are fixed press the 'Go back' button to restart the installation.
+
+
+
+
+
{{category}}
+
+
+ {{item}}
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/install/starterkit.html b/WebCms/Umbraco/Views/install/starterkit.html
new file mode 100644
index 0000000..0b9e22a
--- /dev/null
+++ b/WebCms/Umbraco/Views/install/starterkit.html
@@ -0,0 +1,23 @@
+
+
Install a starter website
+
+
+ Installing a starter website helps you learn how Umbraco works, and gives you a solid
+ and simple foundation to build on top of.
+
diff --git a/WebCms/Umbraco/Views/install/upgrade.html b/WebCms/Umbraco/Views/install/upgrade.html
new file mode 100644
index 0000000..5242fa8
--- /dev/null
+++ b/WebCms/Umbraco/Views/install/upgrade.html
@@ -0,0 +1,20 @@
+
+
Upgrading Umbraco
+
+ Welcome to the Umbraco installer. You see this screen because your Umbraco installation needs a quick upgrade of its database and files, which will ensure your website is kept as fast, secure and up to date as possible.
+
+
+
+ To read a report of changes between your current version {{installer.current.model.currentVersion}} and this version your upgrading to {{installer.current.model.newVersion}}
+
+ Simply click continue below to be guided through the rest of the upgrade
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/install/user.html b/WebCms/Umbraco/Views/install/user.html
new file mode 100644
index 0000000..7be0d23
--- /dev/null
+++ b/WebCms/Umbraco/Views/install/user.html
@@ -0,0 +1,67 @@
+
+
Install Umbraco 7
+
+
Enter your name, email and password to install Umbraco 7 with its default settings, alternatively you can customize your installation
+
+
+
+
diff --git a/WebCms/Umbraco/Views/install/version7upgradereport.html b/WebCms/Umbraco/Views/install/version7upgradereport.html
new file mode 100644
index 0000000..df1e58d
--- /dev/null
+++ b/WebCms/Umbraco/Views/install/version7upgradereport.html
@@ -0,0 +1,22 @@
+
+
Major version upgrade from {{installer.current.model.currentVersion}} to {{installer.current.model.newVersion}}
+
There were {{installer.current.model.errors.length}} issues detected
+
+ The following compatibility issues were found. If you continue all non-compatible property editors will be converted to a Readonly/Label.
+ You will be able to change the property editor to a compatible type manually by editing the data type after installation.
+
+
+ Otherwise if you choose not to proceed you will need to fix the errors listed below.
+ Refer to v{{installer.current.model.newVersion}} upgrade instructions for full details.
+
+
+
+
+ {{item}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/media/create.html b/WebCms/Umbraco/Views/media/create.html
new file mode 100644
index 0000000..db1810f
--- /dev/null
+++ b/WebCms/Umbraco/Views/media/create.html
@@ -0,0 +1,50 @@
+
+
+
+
diff --git a/WebCms/Umbraco/Views/media/delete.html b/WebCms/Umbraco/Views/media/delete.html
new file mode 100644
index 0000000..3c9716e
--- /dev/null
+++ b/WebCms/Umbraco/Views/media/delete.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Are you sure you want to delete{{currentNode.name}} ?
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/media/edit.html b/WebCms/Umbraco/Views/media/edit.html
new file mode 100644
index 0000000..8b478ef
--- /dev/null
+++ b/WebCms/Umbraco/Views/media/edit.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/media/emptyrecyclebin.html b/WebCms/Umbraco/Views/media/emptyrecyclebin.html
new file mode 100644
index 0000000..4558a21
--- /dev/null
+++ b/WebCms/Umbraco/Views/media/emptyrecyclebin.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+ When items are deleted from the recycle bin, they will be gone forever.
+ Are you sure?
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/media/move.html b/WebCms/Umbraco/Views/media/move.html
new file mode 100644
index 0000000..7618acf
--- /dev/null
+++ b/WebCms/Umbraco/Views/media/move.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+ Choose where to move {{currentNode.name}} to in the tree structure below
+
+
+
+
{{error.errorMsg}}
+
{{error.data.Message}}
+
+
+
+
{{currentNode.name}} was moved underneath
+ {{target.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/media/recyclebin.html b/WebCms/Umbraco/Views/media/recyclebin.html
new file mode 100644
index 0000000..4a74e4a
--- /dev/null
+++ b/WebCms/Umbraco/Views/media/recyclebin.html
@@ -0,0 +1,16 @@
+
+
+
diff --git a/WebCms/Umbraco/Views/mediatypes/copy.html b/WebCms/Umbraco/Views/mediatypes/copy.html
new file mode 100644
index 0000000..319e59c
--- /dev/null
+++ b/WebCms/Umbraco/Views/mediatypes/copy.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+ Select the folder to copy{{currentNode.name}}to in the tree structure below
+
You don’t have any packages installed. Either install a local package by selecting it from your machine, or browse through available packages using the "Package" icon in the top right of your screen."
This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100%
diff --git a/WebCms/Umbraco/Views/prevalueeditors/boolean.html b/WebCms/Umbraco/Views/prevalueeditors/boolean.html
new file mode 100644
index 0000000..8af17a1
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/boolean.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/prevalueeditors/decimal.html b/WebCms/Umbraco/Views/prevalueeditors/decimal.html
new file mode 100644
index 0000000..c4e4c95
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/decimal.html
@@ -0,0 +1,11 @@
+
+
+
+ Not a number
+ {{propertyForm.requiredField.errorMsg}}
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/prevalueeditors/hidden.html b/WebCms/Umbraco/Views/prevalueeditors/hidden.html
new file mode 100644
index 0000000..d0ebd99
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/hidden.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/prevalueeditors/imagepicker.html b/WebCms/Umbraco/Views/prevalueeditors/imagepicker.html
new file mode 100644
index 0000000..d9d988d
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/imagepicker.html
@@ -0,0 +1,20 @@
+
diff --git a/WebCms/Umbraco/Views/prevalueeditors/multivalues.html b/WebCms/Umbraco/Views/prevalueeditors/multivalues.html
new file mode 100644
index 0000000..f89efcf
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/multivalues.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/prevalueeditors/nodetype.html b/WebCms/Umbraco/Views/prevalueeditors/nodetype.html
new file mode 100644
index 0000000..d5dcbf9
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/nodetype.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/prevalueeditors/number.html b/WebCms/Umbraco/Views/prevalueeditors/number.html
new file mode 100644
index 0000000..fcdb067
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/number.html
@@ -0,0 +1,11 @@
+
+
+
+ Not a number
+ {{propertyForm.requiredField.errorMsg}}
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/prevalueeditors/radiobuttonlist.html b/WebCms/Umbraco/Views/prevalueeditors/radiobuttonlist.html
new file mode 100644
index 0000000..b82eb88
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/radiobuttonlist.html
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/prevalueeditors/readonlykeyvalues.html b/WebCms/Umbraco/Views/prevalueeditors/readonlykeyvalues.html
new file mode 100644
index 0000000..a9a5558
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/readonlykeyvalues.html
@@ -0,0 +1,7 @@
+
+
+
+ {{preVal.Key}} : {{preVal.Value}}
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/prevalueeditors/requiredfield.html b/WebCms/Umbraco/Views/prevalueeditors/requiredfield.html
new file mode 100644
index 0000000..2d24c35
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/requiredfield.html
@@ -0,0 +1,9 @@
+
+ Use Xpath query to set a root node on the tree, either based on a search from the root of the content tree, or by using a context-aware placeholder.
+
+
+
+ Placeholders finds the nearest published ID and runs its query from there. so for instance:
+
+
$parent/newsArticle
+
+ Will try to get the parent if available, but will then fall back to the nearest ancestor and query for all news articles there.
+
+
+
+ Available placeholders:
+ $current: current page or closest found ancestor
+ $parent: parent page or closest found ancestor
+ $root: root of the content tree
+ $site: Ancestor node at level 1
+
diff --git a/WebCms/Umbraco/Views/prevalueeditors/valuetype.html b/WebCms/Umbraco/Views/prevalueeditors/valuetype.html
new file mode 100644
index 0000000..59b7c55
--- /dev/null
+++ b/WebCms/Umbraco/Views/prevalueeditors/valuetype.html
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/boolean/boolean.html b/WebCms/Umbraco/Views/propertyeditors/boolean/boolean.html
new file mode 100644
index 0000000..2d302d0
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/boolean/boolean.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/changepassword/changepassword.html b/WebCms/Umbraco/Views/propertyeditors/changepassword/changepassword.html
new file mode 100644
index 0000000..aee4211
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/changepassword/changepassword.html
@@ -0,0 +1,62 @@
+
+
+ Password has been reset to:
+
+ {{model.value.generatedPassword}}
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/decimal/decimal.html b/WebCms/Umbraco/Views/propertyeditors/decimal/decimal.html
new file mode 100644
index 0000000..64730bb
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/decimal/decimal.html
@@ -0,0 +1,12 @@
+
+
+
+ Not a number
+ {{propertyForm.requiredField.errorMsg}}
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/dropdown/dropdown.html b/WebCms/Umbraco/Views/propertyeditors/dropdown/dropdown.html
new file mode 100644
index 0000000..e728b3d
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/dropdown/dropdown.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/email/email.html b/WebCms/Umbraco/Views/propertyeditors/email/email.html
new file mode 100644
index 0000000..29c2a1b
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/email/email.html
@@ -0,0 +1,13 @@
+
+
+
+ Required
+ Invalid email
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/entitypicker/entitypicker.html b/WebCms/Umbraco/Views/propertyeditors/entitypicker/entitypicker.html
new file mode 100644
index 0000000..274710b
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/entitypicker/entitypicker.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/fileupload/fileupload.html b/WebCms/Umbraco/Views/propertyeditors/fileupload/fileupload.html
new file mode 100644
index 0000000..1778b7b
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/fileupload/fileupload.html
@@ -0,0 +1,36 @@
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/grid/dialogs/rowdeleteconfirm.html b/WebCms/Umbraco/Views/propertyeditors/grid/dialogs/rowdeleteconfirm.html
new file mode 100644
index 0000000..c69ae2c
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/grid/dialogs/rowdeleteconfirm.html
@@ -0,0 +1,18 @@
+
+
+
Warning!
+
+
+ You are deleting the row configuration '{{model.dialogData.rowName}}'
+
+
+
+ Modifying a row configuration name will result in loss of
+ data for any existing content that is based on this configuration.
+
+
+
+ Are you sure?
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/grid/editors/embed.html b/WebCms/Umbraco/Views/propertyeditors/grid/editors/embed.html
new file mode 100644
index 0000000..e506ac5
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/grid/editors/embed.html
@@ -0,0 +1,17 @@
+
+
+
+
+
Click to embed
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/grid/editors/error.html b/WebCms/Umbraco/Views/propertyeditors/grid/editors/error.html
new file mode 100644
index 0000000..67ef4ea
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/grid/editors/error.html
@@ -0,0 +1,2 @@
+
Something went wrong with this editor, below is the data stored:
+
{{control | json}}
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/grid/editors/macro.html b/WebCms/Umbraco/Views/propertyeditors/grid/editors/macro.html
new file mode 100644
index 0000000..f36a870
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/grid/editors/macro.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
{{title}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/grid/editors/media.html b/WebCms/Umbraco/Views/propertyeditors/grid/editors/media.html
new file mode 100644
index 0000000..dd35757
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/grid/editors/media.html
@@ -0,0 +1,24 @@
+
+
+
+
+
Click to insert image
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/grid/editors/rte.html b/WebCms/Umbraco/Views/propertyeditors/grid/editors/rte.html
new file mode 100644
index 0000000..7bceed6
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/grid/editors/rte.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/grid/editors/textstring.html b/WebCms/Umbraco/Views/propertyeditors/grid/editors/textstring.html
new file mode 100644
index 0000000..18526b0
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/grid/editors/textstring.html
@@ -0,0 +1,9 @@
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/grid/grid.html b/WebCms/Umbraco/Views/propertyeditors/grid/grid.html
new file mode 100644
index 0000000..4ddca47
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/grid/grid.html
@@ -0,0 +1,312 @@
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/integer/integer.html b/WebCms/Umbraco/Views/propertyeditors/integer/integer.html
new file mode 100644
index 0000000..80c6a1e
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/integer/integer.html
@@ -0,0 +1,13 @@
+
+
+
+ Not a number
+ {{propertyForm.requiredField.errorMsg}}
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/bulkActionPermissions.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/listview/bulkActionPermissions.prevalues.html
new file mode 100644
index 0000000..8a5ca61
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/listview/bulkActionPermissions.prevalues.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/includeproperties.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/listview/includeproperties.prevalues.html
new file mode 100644
index 0000000..5cf7de7
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/listview/includeproperties.prevalues.html
@@ -0,0 +1,55 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/layouts.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/listview/layouts.prevalues.html
new file mode 100644
index 0000000..c71f330
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/listview/layouts.prevalues.html
@@ -0,0 +1,57 @@
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/orderDirection.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/listview/orderDirection.prevalues.html
new file mode 100644
index 0000000..6e8ecd4
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/listview/orderDirection.prevalues.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Required
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/sortby.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/listview/sortby.prevalues.html
new file mode 100644
index 0000000..754afd9
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/listview/sortby.prevalues.html
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/macrocontainer/macrocontainer.html b/WebCms/Umbraco/Views/propertyeditors/macrocontainer/macrocontainer.html
new file mode 100644
index 0000000..96c74c9
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/macrocontainer/macrocontainer.html
@@ -0,0 +1,36 @@
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/macrocontainer/macrolist.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/macrocontainer/macrolist.prevalues.html
new file mode 100644
index 0000000..d0081e7
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/macrocontainer/macrolist.prevalues.html
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/markdowneditor/markdowneditor.html b/WebCms/Umbraco/Views/propertyeditors/markdowneditor/markdowneditor.html
new file mode 100644
index 0000000..6b86bf7
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/markdowneditor/markdowneditor.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/mediapicker/mediapicker.html b/WebCms/Umbraco/Views/propertyeditors/mediapicker/mediapicker.html
new file mode 100644
index 0000000..3fbaf62
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/mediapicker/mediapicker.html
@@ -0,0 +1,47 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/memberpicker/memberpicker.html b/WebCms/Umbraco/Views/propertyeditors/memberpicker/memberpicker.html
new file mode 100644
index 0000000..f866beb
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/memberpicker/memberpicker.html
@@ -0,0 +1,33 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/radiobuttons/radiobuttons.html b/WebCms/Umbraco/Views/propertyeditors/radiobuttons/radiobuttons.html
new file mode 100644
index 0000000..2e5ce96
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/radiobuttons/radiobuttons.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/readonlyvalue/readonlyvalue.html b/WebCms/Umbraco/Views/propertyeditors/readonlyvalue/readonlyvalue.html
new file mode 100644
index 0000000..449fa53
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/readonlyvalue/readonlyvalue.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/relatedlinks/relatedlinks.html b/WebCms/Umbraco/Views/propertyeditors/relatedlinks/relatedlinks.html
new file mode 100644
index 0000000..5440d0c
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/relatedlinks/relatedlinks.html
@@ -0,0 +1,98 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/rte/rte.html b/WebCms/Umbraco/Views/propertyeditors/rte/rte.html
new file mode 100644
index 0000000..dcc9425
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/rte/rte.html
@@ -0,0 +1,35 @@
+
+
Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/rte/rte.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/rte/rte.prevalues.html
new file mode 100644
index 0000000..b9d63df
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/rte/rte.prevalues.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+ Pixels
+
+
+
+
+
+ Pixels
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/slider/handle.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/slider/handle.prevalues.html
new file mode 100644
index 0000000..ae5deb0
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/slider/handle.prevalues.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Required
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/slider/orientation.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/slider/orientation.prevalues.html
new file mode 100644
index 0000000..7feed04
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/slider/orientation.prevalues.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Required
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/slider/slider.html b/WebCms/Umbraco/Views/propertyeditors/slider/slider.html
new file mode 100644
index 0000000..e7d9a06
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/slider/slider.html
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/slider/tooltip.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/slider/tooltip.prevalues.html
new file mode 100644
index 0000000..9baf195
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/slider/tooltip.prevalues.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Required
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/tags/tags.html b/WebCms/Umbraco/Views/propertyeditors/tags/tags.html
new file mode 100644
index 0000000..247ae7d
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/tags/tags.html
@@ -0,0 +1,27 @@
+
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/tags/tags.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/tags/tags.prevalues.html
new file mode 100644
index 0000000..2eb9aec
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/tags/tags.prevalues.html
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/templatepicker/templatepicker.html b/WebCms/Umbraco/Views/propertyeditors/templatepicker/templatepicker.html
new file mode 100644
index 0000000..6dad637
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/templatepicker/templatepicker.html
@@ -0,0 +1,3 @@
+
+
{{model.value | json}}
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/test/test.html b/WebCms/Umbraco/Views/propertyeditors/test/test.html
new file mode 100644
index 0000000..19b0972
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/test/test.html
@@ -0,0 +1,18 @@
+
+
+
+ {{item[0].value}}...
+
+
+
+
+
+
+
+
+
+
+
+ json: {{model.value|json}}
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/textarea/textarea.html b/WebCms/Umbraco/Views/propertyeditors/textarea/textarea.html
new file mode 100644
index 0000000..d0896ae
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/textarea/textarea.html
@@ -0,0 +1,3 @@
+
+Required
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/textbox/textbox.html b/WebCms/Umbraco/Views/propertyeditors/textbox/textbox.html
new file mode 100644
index 0000000..defe4ae
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/textbox/textbox.html
@@ -0,0 +1,9 @@
+
+
+
+ Required
+
diff --git a/WebCms/Umbraco/Views/propertyeditors/ultrasimple/ultrasimple.html b/WebCms/Umbraco/Views/propertyeditors/ultrasimple/ultrasimple.html
new file mode 100644
index 0000000..76e31ec
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/ultrasimple/ultrasimple.html
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/urllist/urllist.html b/WebCms/Umbraco/Views/propertyeditors/urllist/urllist.html
new file mode 100644
index 0000000..864ad21
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/urllist/urllist.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Views/propertyeditors/validationtest/validationtest.html b/WebCms/Umbraco/Views/propertyeditors/validationtest/validationtest.html
new file mode 100644
index 0000000..4716a63
--- /dev/null
+++ b/WebCms/Umbraco/Views/propertyeditors/validationtest/validationtest.html
@@ -0,0 +1,12 @@
+
+
Enter a numeric value
+
+
+
+ Required!
+ The value entered is not a number
+ A server error occurred
+
diff --git a/WebCms/Umbraco/Webservices/CMSNode.asmx b/WebCms/Umbraco/Webservices/CMSNode.asmx
new file mode 100644
index 0000000..201a882
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/CMSNode.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="CMSNode.asmx.cs" Class="umbraco.presentation.webservices.CMSNode" %>
diff --git a/WebCms/Umbraco/Webservices/CacheRefresher.asmx b/WebCms/Umbraco/Webservices/CacheRefresher.asmx
new file mode 100644
index 0000000..e2bc295
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/CacheRefresher.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="c#" Codebehind="CacheRefresher.asmx.cs" Class="umbraco.presentation.webservices.CacheRefresher" %>
diff --git a/WebCms/Umbraco/Webservices/CheckForUpgrade.asmx b/WebCms/Umbraco/Webservices/CheckForUpgrade.asmx
new file mode 100644
index 0000000..226022e
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/CheckForUpgrade.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="CheckForUpgrade.asmx.cs" Class="umbraco.presentation.webservices.CheckForUpgrade" %>
diff --git a/WebCms/Umbraco/Webservices/Developer.asmx b/WebCms/Umbraco/Webservices/Developer.asmx
new file mode 100644
index 0000000..b98e33e
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/Developer.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="c#" Codebehind="Developer.asmx.cs" Class="umbraco.webservices.Developer" %>
diff --git a/WebCms/Umbraco/Webservices/MacroContainerService.asmx b/WebCms/Umbraco/Webservices/MacroContainerService.asmx
new file mode 100644
index 0000000..451f468
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/MacroContainerService.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="MacroContainerService.asmx.cs" Class="umbraco.presentation.webservices.MacroContainerService" %>
diff --git a/WebCms/Umbraco/Webservices/MediaUploader.ashx b/WebCms/Umbraco/Webservices/MediaUploader.ashx
new file mode 100644
index 0000000..4d5cb75
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/MediaUploader.ashx
@@ -0,0 +1 @@
+<%@ WebHandler Language="C#" CodeBehind="MediaUploader.ashx.cs" Class="umbraco.presentation.umbraco.webservices.MediaUploader" %>
diff --git a/WebCms/Umbraco/Webservices/Settings.asmx b/WebCms/Umbraco/Webservices/Settings.asmx
new file mode 100644
index 0000000..ed224f1
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/Settings.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="c#" Codebehind="Settings.asmx.cs" Class="umbraco.webservices.Settings" %>
diff --git a/WebCms/Umbraco/Webservices/TagsAutoCompleteHandler.ashx b/WebCms/Umbraco/Webservices/TagsAutoCompleteHandler.ashx
new file mode 100644
index 0000000..0c15355
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/TagsAutoCompleteHandler.ashx
@@ -0,0 +1 @@
+<%@ WebHandler Language="C#" CodeBehind="TagsAutoCompleteHandler.ashx.cs" Class="umbraco.presentation.umbraco.webservices.TagsAutoCompleteHandler" %>
diff --git a/WebCms/Umbraco/Webservices/TreeClientService.asmx b/WebCms/Umbraco/Webservices/TreeClientService.asmx
new file mode 100644
index 0000000..585441d
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/TreeClientService.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="TreeClientService.asmx.cs" Class="umbraco.presentation.webservices.TreeClientService" %>
diff --git a/WebCms/Umbraco/Webservices/TreeDataService.ashx b/WebCms/Umbraco/Webservices/TreeDataService.ashx
new file mode 100644
index 0000000..29e9cd4
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/TreeDataService.ashx
@@ -0,0 +1 @@
+<%@ WebHandler Language="C#" CodeBehind="TreeDataService.ashx.cs" Class="umbraco.presentation.webservices.TreeDataService" %>
diff --git a/WebCms/Umbraco/Webservices/UltimatePickerAutoCompleteHandler.ashx b/WebCms/Umbraco/Webservices/UltimatePickerAutoCompleteHandler.ashx
new file mode 100644
index 0000000..f65459c
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/UltimatePickerAutoCompleteHandler.ashx
@@ -0,0 +1 @@
+<%@ WebHandler Language="C#" CodeBehind="UltimatePickerAutoCompleteHandler.ashx.cs" Class="umbraco.presentation.umbraco.webservices.UltimatePickerAutoCompleteHandler" %>
diff --git a/WebCms/Umbraco/Webservices/ajax.js b/WebCms/Umbraco/Webservices/ajax.js
new file mode 100644
index 0000000..868ccf6
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/ajax.js
@@ -0,0 +1,805 @@
+// ajax.js
+// Common Javascript methods and global objects
+// Ajax framework for Internet Explorer (6.0, ...) and Firefox (1.0, ...)
+// Copyright by Matthias Hertel, http://www.mathertel.de
+// This work is licensed under a Creative Commons Attribution 2.0 Germany License.
+// See http://creativecommons.org/licenses/by/2.0/de/
+// More information on: http://ajaxaspects.blogspot.com/ and http://ajaxaspekte.blogspot.com/
+// -----
+// 05.06.2005 created by Matthias Hertel.
+// 19.06.2005 minor corrections to webservices.
+// 25.06.2005 ajax action queue and timing.
+// 02.07.2005 queue up actions fixed.
+// 10.07.2005 ajax.timeout
+// 10.07.2005 a option object that is passed from ajax.Start() to prepare() is also queued.
+// 10.07.2005 a option object that is passed from ajax.Start() to prepare(), finish()
+// and onException() is also queued.
+// 12.07.2005 correct xml encoding when CallSoap()
+// 20.07.2005 more datatypes and XML Documents
+// 20.07.2005 more datatypes and XML Documents fixed
+// 06.08.2005 caching implemented.
+// 07.08.2005 bugs fixed, when queuing without a delay time.
+// 04.09.2005 bugs fixed, when entering non-multiple actions.
+// 07.09.2005 proxies.IsActive added
+// 27.09.2005 fixed error in handling bool as a datatype
+// 13.12.2005 WebServices with arrays on strings, ints, floats and booleans - still undocumented
+// 27.12.2005 fixed: empty string return values enabled.
+// 27.12.2005 enable the late binding of proxy methods.
+// 21.01.2006 void return bug fixed.
+// 18.02.2006 typo: Finsh -> Finish.
+// 25.02.2006 better xmlhttp request object retrieval, see http://blogs.msdn.com/ie/archive/2006/01/23/516393.aspx
+// 22.04.2006 progress indicator added.
+// 28.01.2006 void return bug fixed again?
+// 09.03.2006 enable late binding of prepare and finish methods by using an expression.
+// 14.07.2006 Safari Browser Version 2.03/Mac OS X 10.4. compatibility: xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue
+// 10.08.2006 date to xml format fixed by Kars Veling
+// 16.09.2006 .postUrl
+
+// ----- global variable for the proxies to webservices. -----
+
+/// The root object for the proxies to webservices.
+var proxies = new Object();
+
+proxies.current = null; // the current active webservice call.
+proxies.xmlhttp = null; // The current active xmlhttp object.
+
+
+// ----- global variable for the ajax engine. -----
+
+/// The root object for the ajax engine.
+var ajax = new Object();
+
+ajax.current = null; /// The current active AJAX action.
+ajax.option = null; /// The options for the current active AJAX action.
+
+ajax.queue = new Array(); /// The pending AJAX actions.
+ajax.options = new Array(); /// The options for the pending AJAX actions.
+
+ajax.timer = null; /// The timer for delayed actions.
+
+ajax.progress = false; /// show a progress indicator
+ajax.progressTimer = null; /// a timer-object that help displaying the progress indicator not too often.
+
+// ----- AJAX engine and actions implementation -----
+
+///Start an AJAX action by entering it into the queue
+ajax.Start = function (action, options) {
+ ajax.Add(action, options);
+ // check if the action should start
+ if ((ajax.current == null) && (ajax.timer == null))
+ ajax._next(false);
+} // ajax.Start
+
+
+///Start an AJAX action by entering it into the queue
+ajax.Add = function (action, options) {
+ if (action == null) {
+ alert("ajax.Start: Argument action must be set.");
+ return;
+ } // if
+
+ // enable the late binding of the methods by using a string that is evaluated.
+ if (typeof(action.call) == "string") action.call = eval(action.call);
+ if (typeof(action.prepare) == "string") action.prepare = eval(action.prepare);
+ if (typeof(action.finish) == "string") action.finish = eval(action.finish);
+
+ if ((action.queueClear != null) && (action.queueClear == true)) {
+ ajax.queue = new Array();
+ ajax.options = new Array();
+
+ } else if ((ajax.queue.length > 0) && ((action.queueMultiple == null) || (action.queueMultiple == false))) {
+ // remove existing action entries from the queue and clear a running timer
+ if ((ajax.timer != null) && (ajax.queue[0] == action)) {
+ window.clearTimeout(ajax.timer);
+ ajax.timer = null;
+ } // if
+
+ var n = 0;
+ while (n < ajax.queue.length) {
+ if (ajax.queue[n] == action) {
+ ajax.queue.splice(n, 1);
+ ajax.options.splice(n, 1);
+ } else {
+ n++;
+ } // if
+ } // while
+ } // if
+
+ if ((action.queueTop == null) || (action.queueTop == false)) {
+ // to the end.
+ ajax.queue.push(action);
+ ajax.options.push(options);
+
+ } else {
+ // to the top
+ ajax.queue.unshift(action);
+ ajax.options.unshift(options);
+ } // if
+} // ajax.Add
+
+
+///Check, if the next AJAX action can start.
+///This is an internal method that should not be called from external.
+///for private use only.
+ajax._next = function (forceStart) {
+ var ca = null // current action
+ var co = null // current opptions
+ var data = null;
+
+ if (ajax.current != null)
+ return; // a call is active: wait more time
+
+ if (ajax.timer != null)
+ return; // a call is pendig: wait more time
+
+ if (ajax.queue.length == 0)
+ return; // nothing to do.
+
+ ca = ajax.queue[0];
+ co = ajax.options[0];
+ if ((forceStart == true) || (ca.delay == null) || (ca.delay == 0)) {
+ // start top action
+ ajax.current = ca;
+ ajax.queue.shift();
+ ajax.option = co;
+ ajax.options.shift();
+
+ // get the data
+ if (ca.prepare != null)
+ try {
+ data = ca.prepare(co);
+ } catch (ex) { }
+
+ if (ca.call != null) {
+ ajax.StartProgress();
+
+ // start the call
+ ca.call.func = ajax.Finish;
+ ca.call.onException = ajax.Exception;
+ ca.call(data);
+ // start timeout timer
+ if (ca.timeout != null)
+ ajax.timer = window.setTimeout(ajax.Cancel, ca.timeout * 1000);
+
+ } else if (ca.postUrl != null) {
+ // post raw data to URL
+
+ } else {
+ // no call
+ ajax.Finish(data);
+ } // if
+
+ } else {
+ // start a timer and wait
+ ajax.timer = window.setTimeout(ajax.EndWait, ca.delay);
+ } // if
+} // ajax._next
+
+
+///The delay time of an action is over.
+ajax.EndWait = function() {
+ ajax.timer = null;
+ ajax._next(true);
+} // ajax.EndWait
+
+
+///The current action timed out.
+ajax.Cancel = function() {
+ proxies.cancel(false); // cancel the current webservice call.
+ ajax.timer = null;
+ ajax.current = null;
+ ajax.option = null;
+ ajax.EndProgress();
+ window.setTimeout(ajax._next, 200); // give some to time to cancel the http connection.
+} // ajax.Cancel
+
+
+///Finish an AJAX Action the normal way
+ajax.Finish = function (data) {
+ // clear timeout timer if set
+ if (ajax.timer != null) {
+ window.clearTimeout(ajax.timer);
+ ajax.timer = null;
+ } // if
+
+ // use the data
+ try {
+ if ((ajax.current != null) && (ajax.current.finish != null))
+ ajax.current.finish(data, ajax.option);
+ } catch (ex) { }
+ // reset the running action
+ ajax.current = null;
+ ajax.option = null;
+ ajax.EndProgress();
+ ajax._next(false)
+} // ajax.Finish
+
+
+///Finish an AJAX Action with an exception
+ajax.Exception = function (ex) {
+ // use the data
+ if (ajax.current.onException != null)
+ ajax.current.onException(ex, ajax.option);
+
+ // reset the running action
+ ajax.current = null;
+ ajax.option = null;
+ ajax.EndProgress();
+} // ajax.Exception
+
+
+///Clear the current and all pending AJAX actions.
+ajax.CancelAll = function () {
+ ajax.Cancel();
+ // clear all pending AJAX actions in the queue.
+ ajax.queue = new Array();
+ ajax.options = new Array();
+} // ajax.CancelAll
+
+
+// ----- show or hide a progress indicator -----
+
+// show a progress indicator if it takes longer...
+ajax.StartProgress = function() {
+ ajax.progress = true;
+ if (ajax.progressTimer != null)
+ window.clearTimeout(ajax.progressTimer);
+ ajax.progressTimer = window.setTimeout(ajax.ShowProgress, 220);
+} // ajax.StartProgress
+
+
+// hide any progress indicator soon.
+ajax.EndProgress = function () {
+ ajax.progress = false;
+ if (ajax.progressTimer != null)
+ window.clearTimeout(ajax.progressTimer);
+ ajax.progressTimer = window.setTimeout(ajax.ShowProgress, 20);
+} // ajax.EndProgress
+
+
+// this function is called by a timer to show or hide a progress indicator
+ajax.ShowProgress = function() {
+ ajax.progressTimer = null;
+ var a = document.getElementById("AjaxProgressIndicator");
+
+ if (ajax.progress && (a != null)) {
+ // just display the existing object
+ a.style.top = document.documentElement.scrollTop + 2 + "px";
+ a.style.display = "";
+
+ } else if (ajax.progress) {
+
+ // find a relative link to the ajaxcore folder containing ajax.js
+ var path = "../ajaxcore/"
+ for (var n in document.scripts) {
+ s = document.scripts[n].src;
+ if ((s != null) && (s.length >= 7) && (s.substr(s.length -7).toLowerCase() == "ajax.js"))
+ path = s.substr(0,s.length -7);
+ } // for
+
+ // create new standard progress object
+ a = document.createElement("div");
+ a.id = "AjaxProgressIndicator";
+ a.style.position = "absolute";
+ a.style.right = "2px";
+ a.style.top = document.documentElement.scrollTop + 2 + "px";
+ a.style.width = "98px";
+ a.style.height = "16px"
+ a.style.padding = "2px";
+ a.style.verticalAlign = "bottom";
+ a.style.backgroundColor="#51c77d";
+
+ a.innerHTML = " please wait...";
+ document.body.appendChild(a);
+
+ } else if (a) {
+ a.style.display="none";
+ } // if
+} // ajax.ShowProgress
+
+
+// ----- simple http-POST server call -----
+
+ajax.postData = function (url, data, func) {
+ var x = proxies._getXHR();
+
+ // enable cookieless sessions:
+ var cs = document.location.href.match(/\/\(.*\)\//);
+ if (cs != null) {
+ url = url.split('/');
+ url[3] += cs[0].substr(0, cs[0].length-1);
+ url = url.join('/');
+ } // if
+
+ x.open("POST", url, (func != null));
+
+ if (func != null) {
+ // async call with xmlhttp-object as parameter
+ x.onreadystatechange = func;
+ x.send(data);
+
+ } else {
+ // sync call
+ x.send(soap);
+ return(x.responseText);
+ } // if
+} // ajax.postData
+
+
+///Execute a soap call.
+///Build the xml for the call of a soap method of a webservice
+///and post it to the server.
+proxies.callSoap = function (args) {
+ var p = args.callee;
+ var x = null;
+
+ // check for existing cache-entry
+ if (p._cache != null) {
+ if ((p.params.length == 1) && (args.length == 1) && (p._cache[args[0]] != null)) {
+ if (p.func != null) {
+ p.func(p._cache[args[0]]);
+ return(null);
+ } else {
+ return(p._cache[args[0]]);
+ } // if
+ } else {
+ p._cachekey = args[0];
+ }// if
+ } // if
+
+ proxies.current = p;
+ x = proxies._getXHR();
+ proxies.xmlhttp = x;
+
+ // envelope start
+ var soap = ""
+ + ""
+ + ""
+ + "<" + p.fname + " xmlns='" + p.service.ns + "'>";
+
+ // parameters
+ for (n = 0; (n < p.params.length) && (n < args.length); n++) {
+ var val = args[n];
+ var typ = p.params[n].split(':');
+
+ if ((typ.length == 1) || (typ[1] == "string")) {
+ val = String(args[n]).replace(/&/g, "&").replace(//g, ">");
+
+ } else if (typ[1] == "int") {
+ val = parseInt(args[n]);
+ } else if (typ[1] == "float") {
+ val = parseFloat(args[n]);
+
+ } else if ((typ[1] == "x") && (typeof(args[n]) == "string")) {
+ val = args[n];
+
+ } else if ((typ[1] == "x") && (typeof(XMLSerializer) != "undefined")) {
+ val = (new XMLSerializer()).serializeToString(args[n].firstChild);
+
+ } else if (typ[1] == "x") {
+ val = args[n].xml;
+
+ } else if ((typ[1] == "bool") && (typeof(args[n]) == "string")) {
+ val = args[n].toLowerCase();
+
+ } else if (typ[1] == "bool") {
+ val = String(args[n]).toLowerCase();
+
+ } else if (typ[1] == "date") {
+ // calculate the xml format for datetime objects from a javascript date object
+ var s, ret;
+ ret = String(val.getFullYear());
+ ret += "-";
+ s = String(val.getMonth() + 1);
+ ret += (s.length == 1 ? "0" + s : s);
+ ret += "-";
+ s = String(val.getDate());
+ ret += (s.length == 1 ? "0" + s : s);
+ ret += "T";
+ s = String(val.getHours());
+ ret += (s.length == 1 ? "0" + s : s);
+ ret += ":";
+ s = String(val.getMinutes());
+ ret += (s.length == 1 ? "0" + s : s);
+ ret += ":";
+ s = String(val.getSeconds());
+ ret += (s.length == 1 ? "0" + s : s);
+ val = ret;
+
+ } else if (typ[1] == "s[]") {
+ val = "" + args[n].join("") + "";
+
+ } else if (typ[1] == "int[]") {
+ val = "" + args[n].join("") + "";
+
+ } else if (typ[1] == "float[]") {
+ val = "" + args[n].join("") + "";
+
+ } else if (typ[1] == "bool[]") {
+ val = "" + args[n].join("") + "";
+
+ } // if
+ soap += "<" + typ[0] + ">" + val + "" + typ[0] + ">"
+ } // for
+
+ // envelope end
+ soap += "" + p.fname + ">"
+ + ""
+ + "";
+
+ // enable cookieless sessions:
+ var u = p.service.url;
+ var cs = document.location.href.match(/\/\(.*\)\//);
+ if (cs != null) {
+ u = p.service.url.split('/');
+ u[3] += cs[0].substr(0, cs[0].length-1);
+ u = u.join('/');
+ } // if
+
+ x.open("POST", u, (p.func != null));
+ x.setRequestHeader("SOAPAction", p.action);
+ x.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
+
+ if (p.corefunc != null) {
+ // async call with xmlhttp-object as parameter
+ x.onreadystatechange = p.corefunc;
+ x.send(soap);
+
+ } else if (p.func != null) {
+ // async call
+ x.onreadystatechange = proxies._response;
+ x.send(soap);
+
+ } else {
+ // sync call
+ x.send(soap);
+ return(proxies._response());
+ } // if
+} // proxies.callSoap
+
+
+// cancel the running webservice call.
+// raise: set raise to false to prevent raising an exception
+proxies.cancel = function(raise) {
+ var cc = proxies.current;
+ var cx = proxies.xmlhttp;
+
+ if (raise == null) raise == true;
+
+ if (proxies.xmlhttp != null) {
+ proxies.xmlhttp.onreadystatechange = function() { };
+ proxies.xmlhttp.abort();
+ if (raise && (proxies.current.onException != null))
+ proxies.current.onException("WebService call was canceled.")
+ proxies.current = null;
+ proxies.xmlhttp = null;
+ } // if
+} // proxies.cancel
+
+
+// px is a proxies.service.func object !
+proxies.EnableCache = function (px) {
+ // attach an empty _cache object.
+ px._cache = new Object();
+} // proxies.EnableCache
+
+
+// check, if a call is currently waiting for a result
+proxies.IsActive = function () {
+ return(proxies.xmlhttp != null);
+} // proxies.IsActive
+
+
+///Callback method for a webservice call that dispatches the response to servive.func or service.onException.
+///for private use only.
+proxies._response = function () {
+ var ret = null;
+ var x = proxies.xmlhttp;
+ var cc = proxies.current;
+ var rtype = null;
+
+ if ((cc.rtype.length > 0) && (cc.rtype[0] != null))
+ rtype = cc.rtype[0].split(':');
+
+ if ((x != null) && (x.readyState == 4)) {
+ if (x.status == 200) {
+ var xNode = null;
+
+ if (rtype != null)
+ xNode = x.responseXML.getElementsByTagName(rtype[0])[0];
+
+ if (xNode == null) {
+ ret = null;
+
+ } else if (xNode.firstChild == null) { // 27.12.2005: empty string return values
+ ret = ((rtype.length == 1) || (rtype[1] == "string") ? "" : null);
+
+ } else if ((rtype.length == 1) || (rtype[1] == "string")) {
+ ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue;
+
+ } else if (rtype[1] == "bool") {
+ ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue;
+ ret = (ret == "true");
+
+ } else if (rtype[1] == "int") {
+ ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue;
+ ret = parseInt(ret);
+
+ } else if (rtype[1] == "float") {
+ ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue;
+ ret = parseFloat(ret);
+
+ } else if ((rtype[1] == "x") && (typeof(XMLSerializer) != "undefined")) {
+ ret = (new XMLSerializer()).serializeToString(xNode.firstChild);
+ ret = ajax._getXMLDOM(ret);
+
+ } else if ((rtype[1] == "ds") && (typeof(XMLSerializer) != "undefined")) {
+ // ret = (new XMLSerializer()).serializeToString(xNode.firstChild.nextSibling.firstChild);
+ ret = (new XMLSerializer()).serializeToString(xNode);
+ ret = ajax._getXMLDOM(ret);
+
+ } else if (rtype[1] == "x") {
+ ret = xNode.firstChild.xml;
+ ret = ajax._getXMLDOM(ret);
+
+ } else if (rtype[1] == "ds") {
+// ret = xNode.firstChild.nextSibling.firstChild.xml;
+ ret = xNode.xml;
+ ret = ajax._getXMLDOM(ret);
+
+ } else if (rtype[1] == "s[]") {
+ // Array of strings
+ ret = new Array();
+ xNode = xNode.firstChild;
+ while (xNode != null) {
+ ret.push(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue);
+ xNode = xNode.nextSibling;
+ } // while
+
+ } else if (rtype[1] == "int[]") {
+ // Array of int
+ ret = new Array();
+ xNode = xNode.firstChild;
+ while (xNode != null) {
+ ret.push(parseInt(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue));
+ xNode = xNode.nextSibling;
+ } // while
+
+ } else if (rtype[1] == "float[]") {
+ // Array of float
+ ret = new Array();
+ xNode = xNode.firstChild;
+ while (xNode != null) {
+ ret.push(parseFloat(xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue));
+ xNode = xNode.nextSibling;
+ } // while
+
+ } else if (rtype[1] == "bool[]") {
+ // Array of bool
+ ret = new Array();
+ xNode = xNode.firstChild;
+ while (xNode != null) {
+ ret.push((xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue).toLowerCase() == "true");
+ xNode = xNode.nextSibling;
+ } // while
+
+ } else {
+ ret = xNode.textContent || xNode.innerText || xNode.text || xNode.childNodes[0].nodeValue;
+ } // if
+
+ // store to _cache
+ if ((cc._cache != null) && (cc._cachekey != null)) {
+ cc._cache[cc._cachekey] = ret;
+ cc._cachekey = null;
+ } // if
+
+ proxies.xmlhttp = null;
+ proxies.current = null;
+
+ if (cc.func == null) {
+ return(ret); // sync
+ } else {
+ cc.func(ret); // async
+ return(null);
+ } // if
+
+ } else if (proxies.current.onException == null) {
+ // no exception
+
+ } else {
+ // raise an exception
+ ret = new Error();
+
+ if (x.status == 404) {
+ ret.message = "The webservice could not be found.";
+
+ } else if (x.status == 500) {
+ ret.name = "SoapException";
+ var n = x.responseXML.documentElement.firstChild.firstChild.firstChild;
+ while (n != null) {
+ if (n.nodeName == "faultcode") ret.message = n.firstChild.nodeValue;
+ if (n.nodeName == "faultstring") ret.description = n.firstChild.nodeValue;
+ n = n.nextSibling;
+ } // while
+
+ } else if ((x.status == 502) || (x.status == 12031)) {
+ ret.message = "The server could not be found.";
+
+ } else {
+ // no classified response.
+ ret.message = "Result-Status:" + x.status + "\n" + x.responseText;
+ } // if
+ proxies.current.onException(ret);
+ } // if
+
+ proxies.xmlhttp = null;
+ proxies.current = null;
+ } // if
+} // proxies._response
+
+
+///Callback method to show the result of a soap call in an alert box.
+///To set up a debug output in an alert box use:
+///proxies.service.method.corefunc = proxies.alertResult;
+proxies.alertResult = function () {
+ var x = proxies.xmlhttp;
+
+ if (x.readyState == 4) {
+ if (x.status == 200) {
+ if (x.responseXML.documentElement.firstChild.firstChild.firstChild == null)
+ alert("(no result)");
+ else
+ alert(x.responseXML.documentElement.firstChild.firstChild.firstChild.firstChild.nodeValue);
+
+ } else if (x.status == 404) { alert("Error!\n\nThe webservice could not be found.");
+
+ } else if (x.status == 500) {
+ // a SoapException
+ var ex = new Error();
+ ex.name = "SoapException";
+ var n = x.responseXML.documentElement.firstChild.firstChild.firstChild;
+ while (n != null) {
+ if (n.nodeName == "faultcode") ex.message = n.firstChild.nodeValue;
+ if (n.nodeName == "faultstring") ex.description = n.firstChild.nodeValue;
+ n = n.nextSibling;
+ } // while
+ alert("The server threw an exception.\n\n" + ex.message + "\n\n" + ex.description);
+
+ } else if (x.status == 502) { alert("Error!\n\nThe server could not be found.");
+
+ } else {
+ // no classified response.
+ alert("Result-Status:" + x.status + "\n" + x.responseText);
+ } // if
+
+ proxies.xmlhttp = null;
+ proxies.current = null;
+ } // if
+} // proxies.alertResult
+
+
+///Show all the details of the returned data of a webservice call.
+///Use this method for debugging transmission problems.
+///To set up a debug output in an alert box use:
+///proxies.service.method.corefunc = proxies.alertResponseText;
+proxies.alertResponseText = function () {
+ if (proxies.xmlhttp.readyState == 4)
+ alert("Status:" + proxies.xmlhttp.status + "\nRESULT:" + proxies.xmlhttp.responseText);
+} // proxies.alertResponseText
+
+
+///show the details about an exception.
+proxies.alertException = function(ex) {
+ var s = "Exception:\n\n";
+
+ if (ex.constructor == String) {
+ s = ex;
+ } else {
+ if ((ex.name != null) && (ex.name != ""))
+ s += "Type: " + ex.name + "\n\n";
+
+ if ((ex.message != null) && (ex.message != ""))
+ s += "Message:\n" + ex.message + "\n\n";
+
+ if ((ex.description != null) && (ex.description != "") && (ex.message != ex.description))
+ s += "Description:\n" + ex.description + "\n\n";
+ } // if
+ alert(s);
+} // proxies.alertException
+
+
+///Get a browser specific implementation of the XMLHttpRequest object.
+// from http://blogs.msdn.com/ie/archive/2006/01/23/516393.aspx
+proxies._getXHR = function () {
+ var x = null;
+ if (window.XMLHttpRequest) {
+ // if IE7, Mozilla, Safari, etc: Use native object
+ x = new XMLHttpRequest()
+
+ } else if (window.ActiveXObject) {
+ // ...otherwise, use the ActiveX control for IE5.x and IE6
+ try { x = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { }
+ if (x == null)
+ try { x = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { }
+ } // if
+ return(x);
+} // proxies._getXHR
+
+
+///Get a browser specific implementation of the XMLDOM object, containing a XML document.
+///the xml document as string.
+ajax._getXMLDOM = function (xmlText) {
+ var obj = null;
+
+ if ((document.implementation != null) && (typeof document.implementation.createDocument == "function")) {
+ // Gecko / Mozilla / Firefox
+ var parser = new DOMParser();
+ obj = parser.parseFromString(xmlText, "text/xml");
+
+ } else {
+ // IE
+ try {
+ obj = new ActiveXObject("MSXML2.DOMDocument");
+ } catch (e) { }
+
+ if (obj == null) {
+ try {
+ obj = new ActiveXObject("Microsoft.XMLDOM");
+ } catch (e) { }
+ } // if
+
+ if (obj != null) {
+ obj.async = false;
+ obj.validateOnParse = false;
+ } // if
+ obj.loadXML(xmlText);
+ } // if
+ return(obj);
+} // _getXMLDOM
+
+
+///show the details of a javascript object.
+///This helps a lot while developing and debugging.
+function inspectObj(obj) {
+ var s = "InspectObj:";
+
+ if (obj == null) {
+ s = "(null)"; alert(s); return;
+ } else if (obj.constructor == String) {
+ s = "\"" + obj + "\"";
+ } else if (obj.constructor == Array) {
+ s += " _ARRAY";
+ } else if (typeof(obj) == "function") {
+ s += " [function]" + obj;
+
+ } else if ((typeof(XMLSerializer) != "undefined") && (obj.constructor == XMLDocument)) {
+ s = "[XMLDocument]:\n" + (new XMLSerializer()).serializeToString(obj.firstChild);
+ alert(s); return;
+
+ } else if ((obj.constructor == null) && (typeof(obj) == "object") && (obj.xml != null)) {
+ s = "[XML]:\n" + obj.xml;
+ alert(s); return;
+ }
+
+ for (p in obj) {
+ try {
+ if (obj[p] == null) {
+ s += "\n" + String(p) + " (...)";
+
+ } else if (typeof(obj[p]) == "function") {
+ s += "\n" + String(p) + " [function]";
+
+ } else if (obj[p].constructor == Array) {
+ s += "\n" + String(p) + " [ARRAY]: " + obj[p];
+ for (n = 0; n < obj[p].length; n++)
+ s += "\n " + n + ": " + obj[p][n];
+
+ } else {
+ s += "\n" + String(p) + " [" + typeof(obj[p]) + "]: " + obj[p];
+ } // if
+ } catch (e) { s+= e;}
+ } // for
+ alert(s);
+} // inspectObj
+
+// ----- End -----
diff --git a/WebCms/Umbraco/Webservices/codeEditorSave.asmx b/WebCms/Umbraco/Webservices/codeEditorSave.asmx
new file mode 100644
index 0000000..947a6fb
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/codeEditorSave.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="codeEditorSave.asmx.cs" Class="umbraco.presentation.webservices.codeEditorSave" %>
diff --git a/WebCms/Umbraco/Webservices/legacyAjaxCalls.asmx b/WebCms/Umbraco/Webservices/legacyAjaxCalls.asmx
new file mode 100644
index 0000000..5acc6e6
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/legacyAjaxCalls.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="legacyAjaxCalls.asmx.cs" Class="umbraco.presentation.webservices.legacyAjaxCalls" %>
diff --git a/WebCms/Umbraco/Webservices/nodeSorter.asmx b/WebCms/Umbraco/Webservices/nodeSorter.asmx
new file mode 100644
index 0000000..ae09ffa
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/nodeSorter.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="nodeSorter.asmx.cs" Class="umbraco.presentation.webservices.nodeSorter" %>
diff --git a/WebCms/Umbraco/Webservices/progressStatus.asmx b/WebCms/Umbraco/Webservices/progressStatus.asmx
new file mode 100644
index 0000000..7b3eebc
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/progressStatus.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="c#" Codebehind="progressStatus.asmx.cs" Class="presentation.umbraco.webservices.progressStatus" %>
diff --git a/WebCms/Umbraco/Webservices/publication.asmx b/WebCms/Umbraco/Webservices/publication.asmx
new file mode 100644
index 0000000..a2ff9b7
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/publication.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="c#" Codebehind="publication.asmx.cs" Class="umbraco.webservices.publication" %>
diff --git a/WebCms/Umbraco/Webservices/templates.asmx b/WebCms/Umbraco/Webservices/templates.asmx
new file mode 100644
index 0000000..4a6e4ee
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/templates.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="c#" Codebehind="templates.asmx.cs" Class="umbraco.webservices.templates" %>
diff --git a/WebCms/Umbraco/Webservices/trashcan.asmx b/WebCms/Umbraco/Webservices/trashcan.asmx
new file mode 100644
index 0000000..19c43da
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/trashcan.asmx
@@ -0,0 +1 @@
+<%@ WebService Language="C#" CodeBehind="trashcan.asmx.cs" Class="umbraco.presentation.webservices.trashcan" %>
diff --git a/WebCms/Umbraco/Webservices/wsdl.xslt b/WebCms/Umbraco/Webservices/wsdl.xslt
new file mode 100644
index 0000000..9e3673c
--- /dev/null
+++ b/WebCms/Umbraco/Webservices/wsdl.xslt
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ // javascript proxy for SOAP based web services
+ // by Matthias Hertel
+ /* */
+
+
+
+
+
+
+ proxies. = {
+ url: "",
+ ns: ""
+ } // proxies.
+
+
+
+
+
+
+
+
+
+
+
+
+ /* inputMessageName='', outputMessageName='' */
+
+
+ /** */
+
+ proxies.. = function () { return(proxies.callSoap(arguments)); }
+ proxies...fname = "";
+ proxies...service = proxies.;
+ proxies...action = "\"\"";
+ proxies...params = [
+
+ ];
+ proxies...rtype = [
+
+ ];
+
+
+
+
+
+
+
+
+
+
+
+ ""
+
+
+
+ ""
+
+
+
+ ":int"
+
+
+
+ ":float"
+
+
+ ":date"
+
+
+
+ ":bool"
+
+
+
+
+ ":s[]"
+
+
+ ":int[]"
+
+
+ ":float[]"
+
+
+ ":bool[]"
+
+
+
+
+
+ ":ds"
+
+
+
+
+ ":x"
+
+
+ ""
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WebCms/Umbraco/Xslt/Templates/Breadcrumb.xslt b/WebCms/Umbraco/Xslt/Templates/Breadcrumb.xslt
new file mode 100644
index 0000000..b5cd15e
--- /dev/null
+++ b/WebCms/Umbraco/Xslt/Templates/Breadcrumb.xslt
@@ -0,0 +1,34 @@
+
+]>
+
+
+
+
+
+
+
+
+
+
+
+
')}]);
\ No newline at end of file
diff --git a/WebCms/Umbraco/lib/angular-dynamic-locale/tmhDynamicLocale.min.js b/WebCms/Umbraco/lib/angular-dynamic-locale/tmhDynamicLocale.min.js
new file mode 100644
index 0000000..53478b9
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular-dynamic-locale/tmhDynamicLocale.min.js
@@ -0,0 +1,8 @@
+/**
+ * Angular Dynamic Locale - 0.1.27
+ * https://github.com/lgalfaso/angular-dynamic-locale
+ * License: MIT
+ */
+
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){"use strict";return angular.module("tmh.dynamicLocale",[]).config(["$provide",function(a){function b(a){return a.$stateful=!0,a}a.decorator("dateFilter",["$delegate",b]),a.decorator("numberFilter",["$delegate",b]),a.decorator("currencyFilter",["$delegate",b])}]).constant("tmhDynamicLocale.STORAGE_KEY","tmhDynamicLocale.locale").provider("tmhDynamicLocale",["tmhDynamicLocale.STORAGE_KEY",function(a){function b(a,b,c,d){var e=document.createElement("script"),f=document.getElementsByTagName("body")[0],g=!1;e.type="text/javascript",e.readyState?e.onreadystatechange=function(){("complete"===e.readyState||"loaded"===e.readyState)&&(e.onreadystatechange=null,d(function(){g||(g=!0,f.removeChild(e),b())},30,!1))}:(e.onload=function(){g||(g=!0,f.removeChild(e),b())},e.onerror=function(){g||(g=!0,f.removeChild(e),c())}),e.src=a,e.async=!0,f.appendChild(e)}function c(a,c,d,g,h,k,l){function m(a,b){f===d&&(angular.forEach(a,function(c,d){b[d]?angular.isArray(b[d])&&(a[d].length=b[d].length):delete a[d]}),angular.forEach(b,function(c,d){angular.isArray(b[d])||angular.isObject(b[d])?(a[d]||(a[d]=angular.isArray(b[d])?[]:{}),m(a[d],b[d])):a[d]=b[d]}))}if(j[d])return f=d,j[d];var n,o=h.defer();return d===f?o.resolve(c):(n=k.get(d))?(f=d,g.$evalAsync(function(){m(c,n),e.put(i,d),g.$broadcast("$localeChangeSuccess",d,c),o.resolve(c)})):(f=d,j[d]=o.promise,b(a,function(){var a=angular.injector(["ngLocale"]),b=a.get("$locale");m(c,b),k.put(d,b),delete j[d],g.$apply(function(){e.put(i,d),g.$broadcast("$localeChangeSuccess",d,c),o.resolve(c)})},function(){delete j[d],g.$apply(function(){f===d&&(f=c.id),g.$broadcast("$localeChangeError",d),o.reject(d)})},l)),o.promise}var d,e,f,g="angular/i18n/angular-locale_{{locale}}.js",h="tmhDynamicLocaleStorageCache",i=a,j={};this.localeLocationPattern=function(a){return a?(g=a,this):g},this.useStorage=function(a){h=a},this.useCookieStorage=function(){this.useStorage("$cookieStore")},this.defaultLocale=function(a){d=a},this.storageKey=function(a){return a?(i=a,this):i},this.$get=["$rootScope","$injector","$interpolate","$locale","$q","tmhDynamicLocaleCache","$timeout",function(a,b,j,k,l,m,n){function o(b){return c(p({locale:b,angularVersion:angular.version.full}),k,b,a,l,m,n)}var p=j(g);return e=b.get(h),a.$evalAsync(function(){var a;(a=e.get(i)||d)&&o(a)}),{set:o,get:function(){return f}}}]}]).provider("tmhDynamicLocaleCache",function(){this.$get=["$cacheFactory",function(a){return a("tmh.dynamicLocales")}]}).provider("tmhDynamicLocaleStorageCache",function(){this.$get=["$cacheFactory",function(a){return a("tmh.dynamicLocales.store")}]}).run(["tmhDynamicLocale",angular.noop]),"tmh.dynamicLocale"});
+//# sourceMappingURL=tmhDynamicLocale.min.js.map
\ No newline at end of file
diff --git a/WebCms/Umbraco/lib/angular-dynamic-locale/tmhDynamicLocale.min.js.map b/WebCms/Umbraco/lib/angular-dynamic-locale/tmhDynamicLocale.min.js.map
new file mode 100644
index 0000000..4ca2884
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular-dynamic-locale/tmhDynamicLocale.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"tmhDynamicLocale.min.js","sources":["src/tmhDynamicLocale.js"],"names":["root","factory","define","amd","exports","module","this","angular","config","$provide","makeStateful","$delegate","$stateful","decorator","constant","provider","STORAGE_KEY","loadScript","url","callback","errorCallback","$timeout","script","document","createElement","body","getElementsByTagName","removed","type","readyState","onreadystatechange","removeChild","onload","onerror","src","async","appendChild","loadLocale","localeUrl","$locale","localeId","$rootScope","$q","localeCache","overrideValues","oldObject","newObject","activeLocale","forEach","value","key","isArray","length","isObject","promiseCache","cachedLocale","deferred","defer","resolve","get","$evalAsync","storage","put","storageKey","$broadcast","promise","localInjector","injector","externalLocale","$apply","id","reject","defaultLocale","localeLocationPattern","storageFactory","useStorage","storageName","useCookieStorage","$get","$injector","interpolate","locale","tmhDynamicLocaleCache","loadLocaleFn","localeLocation","angularVersion","version","full","initialLocale","set","$cacheFactory","run","noop"],"mappings":";;;;;;CAAC,SAAUA,EAAMC,GACO,kBAAXC,SAAyBA,OAAOC,IAEzCD,UAAW,WACT,MAAQD,OAEkB,gBAAZG,SAIhBC,OAAOD,QAAUH,IAEjBA,KAEFK,KAAM,WACR,YAkOA,OAjOAC,SAAQF,OAAO,wBAAyBG,QAAQ,WAAY,SAASC,GACnE,QAASC,GAAaC,GAEpB,MADAA,GAAUC,WAAY,EACfD,EAGTF,EAASI,UAAU,cAAe,YAAaH,IAC/CD,EAASI,UAAU,gBAAiB,YAAaH,IACjDD,EAASI,UAAU,kBAAmB,YAAaH,OAGpDI,SAAS,+BAAgC,2BACzCC,SAAS,oBAAqB,+BAAgC,SAASC,GAgBtE,QAASC,GAAWC,EAAKC,EAAUC,EAAeC,GAChD,GAAIC,GAASC,SAASC,cAAc,UAClCC,EAAOF,SAASG,qBAAqB,QAAQ,GAC7CC,GAAU,CAEZL,GAAOM,KAAO,kBACVN,EAAOO,WACTP,EAAOQ,mBAAqB,YACA,aAAtBR,EAAOO,YACe,WAAtBP,EAAOO,cACTP,EAAOQ,mBAAqB,KAC5BT,EACE,WACMM,IACJA,GAAU,EACVF,EAAKM,YAAYT,GACjBH,MACC,IAAI,MAIbG,EAAOU,OAAS,WACVL,IACJA,GAAU,EACVF,EAAKM,YAAYT,GACjBH,MAEFG,EAAOW,QAAU,WACXN,IACJA,GAAU,EACVF,EAAKM,YAAYT,GACjBF,OAGJE,EAAOY,IAAMhB,EACbI,EAAOa,OAAQ,EACfV,EAAKW,YAAYd,GAcnB,QAASe,GAAWC,EAAWC,EAASC,EAAUC,EAAYC,EAAIC,EAAatB,GAE7E,QAASuB,GAAeC,EAAWC,GAC7BC,IAAiBP,IAGrBjC,QAAQyC,QAAQH,EAAW,SAASI,EAAOC,GACpCJ,EAAUI,GAEJ3C,QAAQ4C,QAAQL,EAAUI,MACnCL,EAAUK,GAAKE,OAASN,EAAUI,GAAKE,cAFhCP,GAAUK,KAKrB3C,QAAQyC,QAAQF,EAAW,SAASG,EAAOC,GACrC3C,QAAQ4C,QAAQL,EAAUI,KAAS3C,QAAQ8C,SAASP,EAAUI,KAC3DL,EAAUK,KACbL,EAAUK,GAAO3C,QAAQ4C,QAAQL,EAAUI,WAE7CN,EAAeC,EAAUK,GAAMJ,EAAUI,KAEzCL,EAAUK,GAAOJ,EAAUI,MAMjC,GAAII,EAAad,GAEf,MADAO,GAAeP,EACRc,EAAad,EAGtB,IAAIe,GACFC,EAAWd,EAAGe,OAwChB,OAvCIjB,KAAaO,EACfS,EAASE,QAAQnB,IACPgB,EAAeZ,EAAYgB,IAAInB,KACzCO,EAAeP,EACfC,EAAWmB,WAAW,WACpBhB,EAAeL,EAASgB,GACxBM,EAAQC,IAAIC,EAAYvB,GACxBC,EAAWuB,WAAW,uBAAwBxB,EAAUD,GACxDiB,EAASE,QAAQnB,OAGnBQ,EAAeP,EACfc,EAAad,GAAYgB,EAASS,QAClChD,EAAWqB,EAAW,WAEpB,GAAI4B,GAAgB3D,QAAQ4D,UAAU,aACpCC,EAAiBF,EAAcP,IAAI,UAErCf,GAAeL,EAAS6B,GACxBzB,EAAYmB,IAAItB,EAAU4B,SACnBd,GAAad,GAEpBC,EAAW4B,OAAO,WAChBR,EAAQC,IAAIC,EAAYvB,GACxBC,EAAWuB,WAAW,uBAAwBxB,EAAUD,GACxDiB,EAASE,QAAQnB,MAElB,iBACMe,GAAad,GAEpBC,EAAW4B,OAAO,WACZtB,IAAiBP,IACnBO,EAAeR,EAAQ+B,IAEzB7B,EAAWuB,WAAW,qBAAsBxB,GAC5CgB,EAASe,OAAO/B,MAEjBnB,IAEEmC,EAASS,QAxIlB,GAAIO,GAGFX,EAGAd,EALA0B,EAAwB,4CACxBC,EAAiB,+BAEjBX,EAAa/C,EACbsC,IAsIFhD,MAAKmE,sBAAwB,SAASxB,GACpC,MAAIA,IACFwB,EAAwBxB,EACjB3C,MAEAmE,GAIXnE,KAAKqE,WAAa,SAASC,GACzBF,EAAiBE,GAGnBtE,KAAKuE,iBAAmB,WACtBvE,KAAKqE,WAAW,iBAGlBrE,KAAKkE,cAAgB,SAASvB,GAC5BuB,EAAgBvB,GAGlB3C,KAAKyD,WAAa,SAASd,GACzB,MAAIA,IACFc,EAAad,EACN3C,MAEAyD,GAIXzD,KAAKwE,MAAQ,aAAc,YAAa,eAAgB,UAAW,KAAM,wBAAyB,WAAY,SAASrC,EAAYsC,EAAWC,EAAaC,EAAQvC,EAAIwC,EAAuB7D,GA4B5L,QAAS8D,GAAa3C,GACpB,MAAOH,GAAW+C,GAAgBH,OAAQzC,EAAU6C,eAAgB9E,QAAQ+E,QAAQC,OAAQN,EAAQzC,EAAUC,EAAYC,EAAIwC,EAAuB7D,GA5BvJ,GAAI+D,GAAiBJ,EAAYP,EASjC,OAPAZ,GAAUkB,EAAUpB,IAAIe,GACxBjC,EAAWmB,WAAW,WACpB,GAAI4B,IACCA,EAAiB3B,EAAQF,IAAII,IAAeS,IAC/CW,EAAaK,MAWfC,IAAKN,EAKLxB,IAAK,WACH,MAAOZ,UAQXhC,SAAS,wBAAyB,WACpCT,KAAKwE,MAAQ,gBAAiB,SAASY,GACrC,MAAOA,GAAc,0BAEtB3E,SAAS,+BAAgC,WAC1CT,KAAKwE,MAAQ,gBAAiB,SAASY,GACrC,MAAOA,GAAc,gCAEtBC,KAAK,mBAAoBpF,QAAQqF,OAE7B"}
\ No newline at end of file
diff --git a/WebCms/Umbraco/lib/angular-local-storage/angular-local-storage.min.js b/WebCms/Umbraco/lib/angular-local-storage/angular-local-storage.min.js
new file mode 100644
index 0000000..b0111f1
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular-local-storage/angular-local-storage.min.js
@@ -0,0 +1,9 @@
+/**
+ * An Angular module that gives you access to the browsers local storage
+ * @version v0.2.6 - 2016-03-16
+ * @link https://github.com/grevory/angular-local-storage
+ * @author grevory
+ * @license MIT License, http://www.opensource.org/licenses/MIT
+ */
+!function(a,b){var c=b.isDefined,d=b.isUndefined,e=b.isNumber,f=b.isObject,g=b.isArray,h=b.extend,i=b.toJson;b.module("LocalStorageModule",[]).provider("localStorageService",function(){this.prefix="ls",this.storageType="localStorage",this.cookie={expiry:30,path:"/"},this.notify={setItem:!0,removeItem:!1},this.setPrefix=function(a){return this.prefix=a,this},this.setStorageType=function(a){return this.storageType=a,this},this.setStorageCookie=function(a,b){return this.cookie.expiry=a,this.cookie.path=b,this},this.setStorageCookieDomain=function(a){return this.cookie.domain=a,this},this.setNotify=function(a,b){return this.notify={setItem:a,removeItem:b},this},this.$get=["$rootScope","$window","$document","$parse",function(a,b,j,k){var l,m=this,n=m.prefix,o=m.cookie,p=m.notify,q=m.storageType;j?j[0]&&(j=j[0]):j=document,"."!==n.substr(-1)&&(n=n?n+".":"");var r=function(a){return n+a},s=function(){try{var c=q in b&&null!==b[q],d=r("__"+Math.round(1e7*Math.random()));return c&&(l=b[q],l.setItem(d,""),l.removeItem(d)),c}catch(e){return q="cookie",a.$broadcast("LocalStorageModule.notification.error",e.message),!1}}(),t=function(b,c){if(c=d(c)?null:i(c),!s||"cookie"===m.storageType)return s||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),p.setItem&&a.$broadcast("LocalStorageModule.notification.setitem",{key:b,newvalue:c,storageType:"cookie"}),z(b,c);try{l&&l.setItem(r(b),c),p.setItem&&a.$broadcast("LocalStorageModule.notification.setitem",{key:b,newvalue:c,storageType:m.storageType})}catch(e){return a.$broadcast("LocalStorageModule.notification.error",e.message),z(b,c)}return!0},u=function(b){if(!s||"cookie"===m.storageType)return s||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),A(b);var c=l?l.getItem(r(b)):null;if(!c||"null"===c)return null;try{return JSON.parse(c)}catch(d){return c}},v=function(){var b,c;for(b=0;b0||(j.cookie="test").indexOf.call(j.cookie,"test")>-1)}catch(c){return a.$broadcast("LocalStorageModule.notification.error",c.message),!1}}(),z=function(b,c,h){if(d(c))return!1;if((g(c)||f(c))&&(c=i(c)),!y)return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;try{var k="",l=new Date,m="";if(null===c?(l.setTime(l.getTime()+-864e5),k="; expires="+l.toGMTString(),c=""):e(h)&&0!==h?(l.setTime(l.getTime()+24*h*60*60*1e3),k="; expires="+l.toGMTString()):0!==o.expiry&&(l.setTime(l.getTime()+24*o.expiry*60*60*1e3),k="; expires="+l.toGMTString()),b){var n="; path="+o.path;o.domain&&(m="; domain="+o.domain),j.cookie=r(b)+"="+encodeURIComponent(c)+k+n+m}}catch(p){return a.$broadcast("LocalStorageModule.notification.error",p.message),!1}return!0},A=function(b){if(!y)return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;for(var c=j.cookie&&j.cookie.split(";")||[],d=0;d
+
+
+
+
+ */
+ factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
+ var cookies = {},
+ lastCookies = {},
+ lastBrowserCookies,
+ runEval = false,
+ copy = angular.copy,
+ isUndefined = angular.isUndefined;
+
+ //creates a poller fn that copies all cookies from the $browser to service & inits the service
+ $browser.addPollFn(function() {
+ var currentCookies = $browser.cookies();
+ if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
+ lastBrowserCookies = currentCookies;
+ copy(currentCookies, lastCookies);
+ copy(currentCookies, cookies);
+ if (runEval) $rootScope.$apply();
+ }
+ })();
+
+ runEval = true;
+
+ //at the end of each eval, push cookies
+ //TODO: this should happen before the "delayed" watches fire, because if some cookies are not
+ // strings or browser refuses to store some cookies, we update the model in the push fn.
+ $rootScope.$watch(push);
+
+ return cookies;
+
+
+ /**
+ * Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
+ */
+ function push() {
+ var name,
+ value,
+ browserCookies,
+ updated;
+
+ //delete any cookies deleted in $cookies
+ for (name in lastCookies) {
+ if (isUndefined(cookies[name])) {
+ $browser.cookies(name, undefined);
+ }
+ }
+
+ //update all cookies updated in $cookies
+ for(name in cookies) {
+ value = cookies[name];
+ if (!angular.isString(value)) {
+ if (angular.isDefined(lastCookies[name])) {
+ cookies[name] = lastCookies[name];
+ } else {
+ delete cookies[name];
+ }
+ } else if (value !== lastCookies[name]) {
+ $browser.cookies(name, value);
+ updated = true;
+ }
+ }
+
+ //verify what was actually stored
+ if (updated){
+ updated = false;
+ browserCookies = $browser.cookies();
+
+ for (name in cookies) {
+ if (cookies[name] !== browserCookies[name]) {
+ //delete or reset all cookies that the browser dropped from $cookies
+ if (isUndefined(browserCookies[name])) {
+ delete cookies[name];
+ } else {
+ cookies[name] = browserCookies[name];
+ }
+ updated = true;
+ }
+ }
+ }
+ }
+ }]).
+
+
+ /**
+ * @ngdoc object
+ * @name ngCookies.$cookieStore
+ * @requires $cookies
+ *
+ * @description
+ * Provides a key-value (string-object) storage, that is backed by session cookies.
+ * Objects put or retrieved from this storage are automatically serialized or
+ * deserialized by angular's toJson/fromJson.
+ * @example
+ */
+ factory('$cookieStore', ['$cookies', function($cookies) {
+
+ return {
+ /**
+ * @ngdoc method
+ * @name ngCookies.$cookieStore#get
+ * @methodOf ngCookies.$cookieStore
+ *
+ * @description
+ * Returns the value of given cookie key
+ *
+ * @param {string} key Id to use for lookup.
+ * @returns {Object} Deserialized cookie value.
+ */
+ get: function(key) {
+ var value = $cookies[key];
+ return value ? angular.fromJson(value) : value;
+ },
+
+ /**
+ * @ngdoc method
+ * @name ngCookies.$cookieStore#put
+ * @methodOf ngCookies.$cookieStore
+ *
+ * @description
+ * Sets a value for given cookie key
+ *
+ * @param {string} key Id for the `value`.
+ * @param {Object} value Value to be stored.
+ */
+ put: function(key, value) {
+ $cookies[key] = angular.toJson(value);
+ },
+
+ /**
+ * @ngdoc method
+ * @name ngCookies.$cookieStore#remove
+ * @methodOf ngCookies.$cookieStore
+ *
+ * @description
+ * Remove given cookie
+ *
+ * @param {string} key Id of the key-value pair to delete.
+ */
+ remove: function(key) {
+ delete $cookies[key];
+ }
+ };
+
+ }]);
+
+
+})(window, window.angular);
diff --git a/WebCms/Umbraco/lib/angular/1.1.5/angular-cookies.min.js b/WebCms/Umbraco/lib/angular/1.1.5/angular-cookies.min.js
new file mode 100644
index 0000000..9d90826
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular/1.1.5/angular-cookies.min.js
@@ -0,0 +1,7 @@
+/*
+ AngularJS v1.1.5
+ (c) 2010-2012 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,i=!1,j=f.copy,k=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,j(a,g),j(a,c),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(c[a])&&b.cookies(a,l);for(a in c)e=c[a],f.isString(e)?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(k(e[a])?delete c[a]:c[a]=e[a])});return c}]).factory("$cookieStore",
+["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular);
diff --git a/WebCms/Umbraco/lib/angular/1.1.5/angular-loader.js b/WebCms/Umbraco/lib/angular/1.1.5/angular-loader.js
new file mode 100644
index 0000000..4d7184f
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular/1.1.5/angular-loader.js
@@ -0,0 +1,304 @@
+/**
+ * @license AngularJS v1.1.5
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+
+(
+
+/**
+ * @ngdoc interface
+ * @name angular.Module
+ * @description
+ *
+ * Interface for configuring angular {@link angular.module modules}.
+ */
+
+function setupModuleLoader(window) {
+
+ function ensure(obj, name, factory) {
+ return obj[name] || (obj[name] = factory());
+ }
+
+ return ensure(ensure(window, 'angular', Object), 'module', function() {
+ /** @type {Object.} */
+ var modules = {};
+
+ /**
+ * @ngdoc function
+ * @name angular.module
+ * @description
+ *
+ * The `angular.module` is a global place for creating and registering Angular modules. All
+ * modules (angular core or 3rd party) that should be available to an application must be
+ * registered using this mechanism.
+ *
+ *
+ * # Module
+ *
+ * A module is a collocation of services, directives, filters, and configuration information. Module
+ * is used to configure the {@link AUTO.$injector $injector}.
+ *
+ *
+ * // Create a new module
+ * var myModule = angular.module('myModule', []);
+ *
+ * // register a new service
+ * myModule.value('appName', 'MyCoolApp');
+ *
+ * // configure existing services inside initialization blocks.
+ * myModule.config(function($locationProvider) {
+'use strict';
+ * // Configure existing providers
+ * $locationProvider.hashPrefix('!');
+ * });
+ *
+ *
+ * Then you can create an injector and load your modules like this:
+ *
+ *
+ * var injector = angular.injector(['ng', 'MyModule'])
+ *
+ *
+ * However it's more likely that you'll just use
+ * {@link ng.directive:ngApp ngApp} or
+ * {@link angular.bootstrap} to simplify this process for you.
+ *
+ * @param {!string} name The name of the module to create or retrieve.
+ * @param {Array.=} requires If specified then new module is being created. If unspecified then the
+ * the module is being retrieved for further configuration.
+ * @param {Function} configFn Optional configuration function for the module. Same as
+ * {@link angular.Module#config Module#config()}.
+ * @returns {module} new module with the {@link angular.Module} api.
+ */
+ return function module(name, requires, configFn) {
+ if (requires && modules.hasOwnProperty(name)) {
+ modules[name] = null;
+ }
+ return ensure(modules, name, function() {
+ if (!requires) {
+ throw Error('No module: ' + name);
+ }
+
+ /** @type {!Array.>} */
+ var invokeQueue = [];
+
+ /** @type {!Array.} */
+ var runBlocks = [];
+
+ var config = invokeLater('$injector', 'invoke');
+
+ /** @type {angular.Module} */
+ var moduleInstance = {
+ // Private state
+ _invokeQueue: invokeQueue,
+ _runBlocks: runBlocks,
+
+ /**
+ * @ngdoc property
+ * @name angular.Module#requires
+ * @propertyOf angular.Module
+ * @returns {Array.} List of module names which must be loaded before this module.
+ * @description
+ * Holds the list of modules which the injector will load before the current module is loaded.
+ */
+ requires: requires,
+
+ /**
+ * @ngdoc property
+ * @name angular.Module#name
+ * @propertyOf angular.Module
+ * @returns {string} Name of the module.
+ * @description
+ */
+ name: name,
+
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#provider
+ * @methodOf angular.Module
+ * @param {string} name service name
+ * @param {Function} providerType Construction function for creating new instance of the service.
+ * @description
+ * See {@link AUTO.$provide#provider $provide.provider()}.
+ */
+ provider: invokeLater('$provide', 'provider'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#factory
+ * @methodOf angular.Module
+ * @param {string} name service name
+ * @param {Function} providerFunction Function for creating new instance of the service.
+ * @description
+ * See {@link AUTO.$provide#factory $provide.factory()}.
+ */
+ factory: invokeLater('$provide', 'factory'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#service
+ * @methodOf angular.Module
+ * @param {string} name service name
+ * @param {Function} constructor A constructor function that will be instantiated.
+ * @description
+ * See {@link AUTO.$provide#service $provide.service()}.
+ */
+ service: invokeLater('$provide', 'service'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#value
+ * @methodOf angular.Module
+ * @param {string} name service name
+ * @param {*} object Service instance object.
+ * @description
+ * See {@link AUTO.$provide#value $provide.value()}.
+ */
+ value: invokeLater('$provide', 'value'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#constant
+ * @methodOf angular.Module
+ * @param {string} name constant name
+ * @param {*} object Constant value.
+ * @description
+ * Because the constant are fixed, they get applied before other provide methods.
+ * See {@link AUTO.$provide#constant $provide.constant()}.
+ */
+ constant: invokeLater('$provide', 'constant', 'unshift'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#animation
+ * @methodOf angular.Module
+ * @param {string} name animation name
+ * @param {Function} animationFactory Factory function for creating new instance of an animation.
+ * @description
+ *
+ * Defines an animation hook that can be later used with {@link ng.directive:ngAnimate ngAnimate}
+ * alongside {@link ng.directive:ngAnimate#Description common ng directives} as well as custom directives.
+ *
+ * module.animation('animation-name', function($inject1, $inject2) {
+ * return {
+ * //this gets called in preparation to setup an animation
+ * setup : function(element) { ... },
+ *
+ * //this gets called once the animation is run
+ * start : function(element, done, memo) { ... }
+ * }
+ * })
+ *
+ *
+ * See {@link ng.$animationProvider#register $animationProvider.register()} and
+ * {@link ng.directive:ngAnimate ngAnimate} for more information.
+ */
+ animation: invokeLater('$animationProvider', 'register'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#filter
+ * @methodOf angular.Module
+ * @param {string} name Filter name.
+ * @param {Function} filterFactory Factory function for creating new instance of filter.
+ * @description
+ * See {@link ng.$filterProvider#register $filterProvider.register()}.
+ */
+ filter: invokeLater('$filterProvider', 'register'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#controller
+ * @methodOf angular.Module
+ * @param {string} name Controller name.
+ * @param {Function} constructor Controller constructor function.
+ * @description
+ * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
+ */
+ controller: invokeLater('$controllerProvider', 'register'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#directive
+ * @methodOf angular.Module
+ * @param {string} name directive name
+ * @param {Function} directiveFactory Factory function for creating new instance of
+ * directives.
+ * @description
+ * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
+ */
+ directive: invokeLater('$compileProvider', 'directive'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#config
+ * @methodOf angular.Module
+ * @param {Function} configFn Execute this function on module load. Useful for service
+ * configuration.
+ * @description
+ * Use this method to register work which needs to be performed on module loading.
+ */
+ config: config,
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#run
+ * @methodOf angular.Module
+ * @param {Function} initializationFn Execute this function after injector creation.
+ * Useful for application initialization.
+ * @description
+ * Use this method to register work which should be performed when the injector is done
+ * loading all modules.
+ */
+ run: function(block) {
+ runBlocks.push(block);
+ return this;
+ }
+ };
+
+ if (configFn) {
+ config(configFn);
+ }
+
+ return moduleInstance;
+
+ /**
+ * @param {string} provider
+ * @param {string} method
+ * @param {String=} insertMethod
+ * @returns {angular.Module}
+ */
+ function invokeLater(provider, method, insertMethod) {
+ return function() {
+ invokeQueue[insertMethod || 'push']([provider, method, arguments]);
+ return moduleInstance;
+ }
+ }
+ });
+ };
+ });
+
+}
+
+)(window);
+
+/**
+ * Closure compiler type information
+ *
+ * @typedef { {
+ * requires: !Array.,
+ * invokeQueue: !Array.>,
+ *
+ * service: function(string, Function):angular.Module,
+ * factory: function(string, Function):angular.Module,
+ * value: function(string, *):angular.Module,
+ *
+ * filter: function(string, Function):angular.Module,
+ *
+ * init: function(Function):angular.Module
+ * } }
+ */
+angular.Module;
+
diff --git a/WebCms/Umbraco/lib/angular/1.1.5/angular-loader.min.js b/WebCms/Umbraco/lib/angular/1.1.5/angular-loader.min.js
new file mode 100644
index 0000000..0c262d4
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular/1.1.5/angular-loader.min.js
@@ -0,0 +1,7 @@
+/*
+ AngularJS v1.1.5
+ (c) 2010-2012 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(i){'use strict';function d(c,b,e){return c[b]||(c[b]=e())}return d(d(i,"angular",Object),"module",function(){var c={};return function(b,e,f){e&&c.hasOwnProperty(b)&&(c[b]=null);return d(c,b,function(){function a(a,b,d){return function(){c[d||"push"]([a,b,arguments]);return g}}if(!e)throw Error("No module: "+b);var c=[],d=[],h=a("$injector","invoke"),g={_invokeQueue:c,_runBlocks:d,requires:e,name:b,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),
+value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animationProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:h,run:function(a){d.push(a);return this}};f&&h(f);return g})}})})(window);
diff --git a/WebCms/Umbraco/lib/angular/1.1.5/angular-mobile.js b/WebCms/Umbraco/lib/angular/1.1.5/angular-mobile.js
new file mode 100644
index 0000000..8cfb20c
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular/1.1.5/angular-mobile.js
@@ -0,0 +1,462 @@
+/**
+ * @license AngularJS v1.1.5
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular, undefined) {
+'use strict';
+
+/**
+ * @ngdoc overview
+ * @name ngMobile
+ * @description
+ * Touch events and other mobile helpers.
+ * Based on jQuery Mobile touch event handling (jquerymobile.com)
+ */
+
+// define ngMobile module
+var ngMobile = angular.module('ngMobile', []);
+
+/**
+ * @ngdoc directive
+ * @name ngMobile.directive:ngClick
+ *
+ * @description
+ * A more powerful replacement for the default ngClick designed to be used on touchscreen
+ * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
+ * the click event. This version handles them immediately, and then prevents the
+ * following click event from propagating.
+ *
+ * This directive can fall back to using an ordinary click event, and so works on desktop
+ * browsers as well as mobile.
+ *
+ * This directive also sets the CSS class `ng-click-active` while the element is being held
+ * down (by a mouse click or touch) so you can restyle the depressed element if you wish.
+ *
+ * @element ANY
+ * @param {expression} ngClick {@link guide/expression Expression} to evaluate
+ * upon tap. (Event object is available as `$event`)
+ *
+ * @example
+
+
+
+ count: {{ count }}
+
+
+ */
+
+ngMobile.config(['$provide', function($provide) {
+ $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
+ // drop the default ngClick directive
+ $delegate.shift();
+ return $delegate;
+ }]);
+}]);
+
+ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',
+ function($parse, $timeout, $rootElement) {
+ var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
+ var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
+ var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
+ var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
+
+ var ACTIVE_CLASS_NAME = 'ng-click-active';
+ var lastPreventedTime;
+ var touchCoordinates;
+
+
+ // TAP EVENTS AND GHOST CLICKS
+ //
+ // Why tap events?
+ // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
+ // double-tapping, and then fire a click event.
+ //
+ // This delay sucks and makes mobile apps feel unresponsive.
+ // So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
+ // the user has tapped on something.
+ //
+ // What happens when the browser then generates a click event?
+ // The browser, of course, also detects the tap and fires a click after a delay. This results in
+ // tapping/clicking twice. So we do "clickbusting" to prevent it.
+ //
+ // How does it work?
+ // We attach global touchstart and click handlers, that run during the capture (early) phase.
+ // So the sequence for a tap is:
+ // - global touchstart: Sets an "allowable region" at the point touched.
+ // - element's touchstart: Starts a touch
+ // (- touchmove or touchcancel ends the touch, no click follows)
+ // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
+ // too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
+ // - preventGhostClick() removes the allowable region the global touchstart created.
+ // - The browser generates a click event.
+ // - The global click handler catches the click, and checks whether it was in an allowable region.
+ // - If preventGhostClick was called, the region will have been removed, the click is busted.
+ // - If the region is still there, the click proceeds normally. Therefore clicks on links and
+ // other elements without ngTap on them work normally.
+ //
+ // This is an ugly, terrible hack!
+ // Yeah, tell me about it. The alternatives are using the slow click events, or making our users
+ // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
+ // encapsulates this ugly logic away from the user.
+ //
+ // Why not just put click handlers on the element?
+ // We do that too, just to be sure. The problem is that the tap event might have caused the DOM
+ // to change, so that the click fires in the same position but something else is there now. So
+ // the handlers are global and care only about coordinates and not elements.
+
+ // Checks if the coordinates are close enough to be within the region.
+ function hit(x1, y1, x2, y2) {
+ return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
+ }
+
+ // Checks a list of allowable regions against a click location.
+ // Returns true if the click should be allowed.
+ // Splices out the allowable region from the list after it has been used.
+ function checkAllowableRegions(touchCoordinates, x, y) {
+ for (var i = 0; i < touchCoordinates.length; i += 2) {
+ if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
+ touchCoordinates.splice(i, i + 2);
+ return true; // allowable region
+ }
+ }
+ return false; // No allowable region; bust it.
+ }
+
+ // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
+ // was called recently.
+ function onClick(event) {
+ if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
+ return; // Too old.
+ }
+
+ var touches = event.touches && event.touches.length ? event.touches : [event];
+ var x = touches[0].clientX;
+ var y = touches[0].clientY;
+ // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
+ // and on the input element). Depending on the exact browser, this second click we don't want
+ // to bust has either (0,0) or negative coordinates.
+ if (x < 1 && y < 1) {
+ return; // offscreen
+ }
+
+ // Look for an allowable region containing this click.
+ // If we find one, that means it was created by touchstart and not removed by
+ // preventGhostClick, so we don't bust it.
+ if (checkAllowableRegions(touchCoordinates, x, y)) {
+ return;
+ }
+
+ // If we didn't find an allowable region, bust the click.
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+
+ // Global touchstart handler that creates an allowable region for a click event.
+ // This allowable region can be removed by preventGhostClick if we want to bust it.
+ function onTouchStart(event) {
+ var touches = event.touches && event.touches.length ? event.touches : [event];
+ var x = touches[0].clientX;
+ var y = touches[0].clientY;
+ touchCoordinates.push(x, y);
+
+ $timeout(function() {
+ // Remove the allowable region.
+ for (var i = 0; i < touchCoordinates.length; i += 2) {
+ if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
+ touchCoordinates.splice(i, i + 2);
+ return;
+ }
+ }
+ }, PREVENT_DURATION, false);
+ }
+
+ // On the first call, attaches some event handlers. Then whenever it gets called, it creates a
+ // zone around the touchstart where clicks will get busted.
+ function preventGhostClick(x, y) {
+ if (!touchCoordinates) {
+ $rootElement[0].addEventListener('click', onClick, true);
+ $rootElement[0].addEventListener('touchstart', onTouchStart, true);
+ touchCoordinates = [];
+ }
+
+ lastPreventedTime = Date.now();
+
+ checkAllowableRegions(touchCoordinates, x, y);
+ }
+
+ // Actual linking function.
+ return function(scope, element, attr) {
+ var clickHandler = $parse(attr.ngClick),
+ tapping = false,
+ tapElement, // Used to blur the element after a tap.
+ startTime, // Used to check if the tap was held too long.
+ touchStartX,
+ touchStartY;
+
+ function resetState() {
+ tapping = false;
+ element.removeClass(ACTIVE_CLASS_NAME);
+ }
+
+ element.bind('touchstart', function(event) {
+ tapping = true;
+ tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
+ // Hack for Safari, which can target text nodes instead of containers.
+ if(tapElement.nodeType == 3) {
+ tapElement = tapElement.parentNode;
+ }
+
+ element.addClass(ACTIVE_CLASS_NAME);
+
+ startTime = Date.now();
+
+ var touches = event.touches && event.touches.length ? event.touches : [event];
+ var e = touches[0].originalEvent || touches[0];
+ touchStartX = e.clientX;
+ touchStartY = e.clientY;
+ });
+
+ element.bind('touchmove', function(event) {
+ resetState();
+ });
+
+ element.bind('touchcancel', function(event) {
+ resetState();
+ });
+
+ element.bind('touchend', function(event) {
+ var diff = Date.now() - startTime;
+
+ var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
+ ((event.touches && event.touches.length) ? event.touches : [event]);
+ var e = touches[0].originalEvent || touches[0];
+ var x = e.clientX;
+ var y = e.clientY;
+ var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
+
+ if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
+ // Call preventGhostClick so the clickbuster will catch the corresponding click.
+ preventGhostClick(x, y);
+
+ // Blur the focused element (the button, probably) before firing the callback.
+ // This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
+ // I couldn't get anything to work reliably on Android Chrome.
+ if (tapElement) {
+ tapElement.blur();
+ }
+
+ scope.$apply(function() {
+ // TODO(braden): This is sending the touchend, not a tap or click. Is that kosher?
+ clickHandler(scope, {$event: event});
+ });
+ }
+
+ resetState();
+ });
+
+ // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
+ // something else nearby.
+ element.onclick = function(event) { };
+
+ // Fallback click handler.
+ // Busted clicks don't get this far, and adding this handler allows ng-tap to be used on
+ // desktop as well, to allow more portable sites.
+ element.bind('click', function(event) {
+ scope.$apply(function() {
+ clickHandler(scope, {$event: event});
+ });
+ });
+
+/*
+ element.bind('mousedown', function(event) {
+ element.addClass(ACTIVE_CLASS_NAME);
+ });
+
+ element.bind('mousemove mouseup', function(event) {
+ element.removeClass(ACTIVE_CLASS_NAME);
+ });
+*/
+
+ };
+}]);
+
+/**
+ * @ngdoc directive
+ * @name ngMobile.directive:ngSwipeLeft
+ *
+ * @description
+ * Specify custom behavior when an element is swiped to the left on a touchscreen device.
+ * A leftward swipe is a quick, right-to-left slide of the finger.
+ * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag too.
+ *
+ * @element ANY
+ * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate
+ * upon left swipe. (Event object is available as `$event`)
+ *
+ * @example
+
+
+
+ Some list content, like an email in the inbox
+
+
+
+
+
+
+
+ */
+
+/**
+ * @ngdoc directive
+ * @name ngMobile.directive:ngSwipeRight
+ *
+ * @description
+ * Specify custom behavior when an element is swiped to the right on a touchscreen device.
+ * A rightward swipe is a quick, left-to-right slide of the finger.
+ * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag too.
+ *
+ * @element ANY
+ * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate
+ * upon right swipe. (Event object is available as `$event`)
+ *
+ * @example
+
+
+
+ Some list content, like an email in the inbox
+
+
+
+
+
+
+
+ */
+
+function makeSwipeDirective(directiveName, direction) {
+ ngMobile.directive(directiveName, ['$parse', function($parse) {
+ // The maximum vertical delta for a swipe should be less than 75px.
+ var MAX_VERTICAL_DISTANCE = 75;
+ // Vertical distance should not be more than a fraction of the horizontal distance.
+ var MAX_VERTICAL_RATIO = 0.3;
+ // At least a 30px lateral motion is necessary for a swipe.
+ var MIN_HORIZONTAL_DISTANCE = 30;
+ // The total distance in any direction before we make the call on swipe vs. scroll.
+ var MOVE_BUFFER_RADIUS = 10;
+
+ function getCoordinates(event) {
+ var touches = event.touches && event.touches.length ? event.touches : [event];
+ var e = (event.changedTouches && event.changedTouches[0]) ||
+ (event.originalEvent && event.originalEvent.changedTouches &&
+ event.originalEvent.changedTouches[0]) ||
+ touches[0].originalEvent || touches[0];
+
+ return {
+ x: e.clientX,
+ y: e.clientY
+ };
+ }
+
+ return function(scope, element, attr) {
+ var swipeHandler = $parse(attr[directiveName]);
+ var startCoords, valid;
+ var totalX, totalY;
+ var lastX, lastY;
+
+ function validSwipe(event) {
+ // Check that it's within the coordinates.
+ // Absolute vertical distance must be within tolerances.
+ // Horizontal distance, we take the current X - the starting X.
+ // This is negative for leftward swipes and positive for rightward swipes.
+ // After multiplying by the direction (-1 for left, +1 for right), legal swipes
+ // (ie. same direction as the directive wants) will have a positive delta and
+ // illegal ones a negative delta.
+ // Therefore this delta must be positive, and larger than the minimum.
+ if (!startCoords) return false;
+ var coords = getCoordinates(event);
+ var deltaY = Math.abs(coords.y - startCoords.y);
+ var deltaX = (coords.x - startCoords.x) * direction;
+ return valid && // Short circuit for already-invalidated swipes.
+ deltaY < MAX_VERTICAL_DISTANCE &&
+ deltaX > 0 &&
+ deltaX > MIN_HORIZONTAL_DISTANCE &&
+ deltaY / deltaX < MAX_VERTICAL_RATIO;
+ }
+
+ element.bind('touchstart mousedown', function(event) {
+ startCoords = getCoordinates(event);
+ valid = true;
+ totalX = 0;
+ totalY = 0;
+ lastX = startCoords.x;
+ lastY = startCoords.y;
+ });
+
+ element.bind('touchcancel', function(event) {
+ valid = false;
+ });
+
+ element.bind('touchmove mousemove', function(event) {
+ if (!valid) return;
+
+ // Android will send a touchcancel if it thinks we're starting to scroll.
+ // So when the total distance (+ or - or both) exceeds 10px in either direction,
+ // we either:
+ // - On totalX > totalY, we send preventDefault() and treat this as a swipe.
+ // - On totalY > totalX, we let the browser handle it as a scroll.
+
+ // Invalidate a touch while it's in progress if it strays too far away vertically.
+ // We don't want a scroll down and back up while drifting sideways to be a swipe just
+ // because you happened to end up vertically close in the end.
+ if (!startCoords) return;
+ var coords = getCoordinates(event);
+
+ if (Math.abs(coords.y - startCoords.y) > MAX_VERTICAL_DISTANCE) {
+ valid = false;
+ return;
+ }
+
+ totalX += Math.abs(coords.x - lastX);
+ totalY += Math.abs(coords.y - lastY);
+
+ lastX = coords.x;
+ lastY = coords.y;
+
+ if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) {
+ return;
+ }
+
+ // One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
+ if (totalY > totalX) {
+ valid = false;
+ return;
+ } else {
+ event.preventDefault();
+ }
+ });
+
+ element.bind('touchend mouseup', function(event) {
+ if (validSwipe(event)) {
+ // Prevent this swipe from bubbling up to any other elements with ngSwipes.
+ event.stopPropagation();
+ scope.$apply(function() {
+ swipeHandler(scope, {$event:event});
+ });
+ }
+ });
+ };
+ }]);
+}
+
+// Left is negative X-coordinate, right is positive.
+makeSwipeDirective('ngSwipeLeft', -1);
+makeSwipeDirective('ngSwipeRight', 1);
+
+
+
+})(window, window.angular);
diff --git a/WebCms/Umbraco/lib/angular/1.1.5/angular-mobile.min.js b/WebCms/Umbraco/lib/angular/1.1.5/angular-mobile.min.js
new file mode 100644
index 0000000..bc777ea
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular/1.1.5/angular-mobile.min.js
@@ -0,0 +1,11 @@
+/*
+ AngularJS v1.1.5
+ (c) 2010-2012 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(u,s){'use strict';function k(i,t){j.directive(i,["$parse",function(l){function g(b){var h=b.touches&&b.touches.length?b.touches:[b],b=b.changedTouches&&b.changedTouches[0]||b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]||h[0].originalEvent||h[0];return{x:b.clientX,y:b.clientY}}var m=75,j=0.3,p=30;return function(b,h,n){function o(e){if(!a)return!1;var b=g(e),e=Math.abs(b.y-a.y),b=(b.x-a.x)*t;return c&&e0&&b>p&&e/bm?c=!1:(f+=Math.abs(d.x-q),e+=Math.abs(d.y-r),q=d.x,r=d.y,f<10&&e<10||(e>f?c=!1:b.preventDefault()))}});h.bind("touchend mouseup",function(a){o(a)&&(a.stopPropagation(),b.$apply(function(){d(b,{$event:a})}))})}}])}var j=s.module("ngMobile",[]);j.config(["$provide",function(i){i.decorator("ngClickDirective",["$delegate",function(i){i.shift();
+return i}])}]);j.directive("ngClick",["$parse","$timeout","$rootElement",function(i,j,l){function g(a,c,b){for(var e=0;eb)){var c=a.touches&&a.touches.length?a.touches:[a],f=c[0].clientX,c=c[0].clientY;!(f<1&&c<1)&&!g(d,f,c)&&(a.stopPropagation(),a.preventDefault())}}function k(a){var a=a.touches&&a.touches.length?a.touches:[a],c=a[0].clientX,f=a[0].clientY;d.push(c,f);j(function(){for(var a=
+0;a
+ * describe('$exceptionHandlerProvider', function() {
+ *
+ * it('should capture log messages and exceptions', function() {
+ *
+ * module(function($exceptionHandlerProvider) {
+ * $exceptionHandlerProvider.mode('log');
+ * });
+ *
+ * inject(function($log, $exceptionHandler, $timeout) {
+ * $timeout(function() { $log.log(1); });
+ * $timeout(function() { $log.log(2); throw 'banana peel'; });
+ * $timeout(function() { $log.log(3); });
+ * expect($exceptionHandler.errors).toEqual([]);
+ * expect($log.assertEmpty());
+ * $timeout.flush();
+ * expect($exceptionHandler.errors).toEqual(['banana peel']);
+ * expect($log.log.logs).toEqual([[1], [2], [3]]);
+ * });
+ * });
+ * });
+ *
+ */
+
+angular.mock.$ExceptionHandlerProvider = function() {
+ var handler;
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$exceptionHandlerProvider#mode
+ * @methodOf ngMock.$exceptionHandlerProvider
+ *
+ * @description
+ * Sets the logging mode.
+ *
+ * @param {string} mode Mode of operation, defaults to `rethrow`.
+ *
+ * - `rethrow`: If any errors are passed into the handler in tests, it typically
+ * means that there is a bug in the application or test, so this mock will
+ * make these tests fail.
+ * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an
+ * array of errors in `$exceptionHandler.errors`, to allow later assertion of them.
+ * See {@link ngMock.$log#assertEmpty assertEmpty()} and
+ * {@link ngMock.$log#reset reset()}
+ */
+ this.mode = function(mode) {
+ switch(mode) {
+ case 'rethrow':
+ handler = function(e) {
+ throw e;
+ };
+ break;
+ case 'log':
+ var errors = [];
+
+ handler = function(e) {
+ if (arguments.length == 1) {
+ errors.push(e);
+ } else {
+ errors.push([].slice.call(arguments, 0));
+ }
+ };
+
+ handler.errors = errors;
+ break;
+ default:
+ throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
+ }
+ };
+
+ this.$get = function() {
+ return handler;
+ };
+
+ this.mode('rethrow');
+};
+
+
+/**
+ * @ngdoc service
+ * @name ngMock.$log
+ *
+ * @description
+ * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays
+ * (one array per logging level). These arrays are exposed as `logs` property of each of the
+ * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`.
+ *
+ */
+angular.mock.$LogProvider = function() {
+
+ function concat(array1, array2, index) {
+ return array1.concat(Array.prototype.slice.call(array2, index));
+ }
+
+
+ this.$get = function () {
+ var $log = {
+ log: function() { $log.log.logs.push(concat([], arguments, 0)); },
+ warn: function() { $log.warn.logs.push(concat([], arguments, 0)); },
+ info: function() { $log.info.logs.push(concat([], arguments, 0)); },
+ error: function() { $log.error.logs.push(concat([], arguments, 0)); }
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$log#reset
+ * @methodOf ngMock.$log
+ *
+ * @description
+ * Reset all of the logging arrays to empty.
+ */
+ $log.reset = function () {
+ /**
+ * @ngdoc property
+ * @name ngMock.$log#log.logs
+ * @propertyOf ngMock.$log
+ *
+ * @description
+ * Array of messages logged using {@link ngMock.$log#log}.
+ *
+ * @example
+ *
+ * $log.log('Some Log');
+ * var first = $log.log.logs.unshift();
+ *
+ * $log.log('Some Error');
+ * var first = $log.error.logs.unshift();
+ *
+ */
+ $log.error.logs = [];
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$log#assertEmpty
+ * @methodOf ngMock.$log
+ *
+ * @description
+ * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown.
+ */
+ $log.assertEmpty = function() {
+ var errors = [];
+ angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) {
+ angular.forEach($log[logLevel].logs, function(log) {
+ angular.forEach(log, function (logItem) {
+ errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || ''));
+ });
+ });
+ });
+ if (errors.length) {
+ errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " +
+ "log message was not checked and removed:");
+ errors.push('');
+ throw new Error(errors.join('\n---------\n'));
+ }
+ };
+
+ $log.reset();
+ return $log;
+ };
+};
+
+
+(function() {
+ var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
+
+ function jsonStringToDate(string){
+ var match;
+ if (match = string.match(R_ISO8061_STR)) {
+ var date = new Date(0),
+ tzHour = 0,
+ tzMin = 0;
+ if (match[9]) {
+ tzHour = int(match[9] + match[10]);
+ tzMin = int(match[9] + match[11]);
+ }
+ date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
+ date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
+ return date;
+ }
+ return string;
+ }
+
+ function int(str) {
+ return parseInt(str, 10);
+ }
+
+ function padNumber(num, digits, trim) {
+ var neg = '';
+ if (num < 0) {
+ neg = '-';
+ num = -num;
+ }
+ num = '' + num;
+ while(num.length < digits) num = '0' + num;
+ if (trim)
+ num = num.substr(num.length - digits);
+ return neg + num;
+ }
+
+
+ /**
+ * @ngdoc object
+ * @name angular.mock.TzDate
+ * @description
+ *
+ * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
+ *
+ * Mock of the Date type which has its timezone specified via constructor arg.
+ *
+ * The main purpose is to create Date-like instances with timezone fixed to the specified timezone
+ * offset, so that we can test code that depends on local timezone settings without dependency on
+ * the time zone settings of the machine where the code is running.
+ *
+ * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
+ * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
+ *
+ * @example
+ * !!!! WARNING !!!!!
+ * This is not a complete Date object so only methods that were implemented can be called safely.
+ * To make matters worse, TzDate instances inherit stuff from Date via a prototype.
+ *
+ * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
+ * incomplete we might be missing some non-standard methods. This can result in errors like:
+ * "Date.prototype.foo called on incompatible Object".
+ *
+ *
+ beforeEach(module(function($provide) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+ }));
+
+ it('should do something', inject(function($window) {
+ var val = null;
+ $window.setTimeout(function() { val = 123; }, 10);
+ expect(val).toEqual(null);
+ window.setTimeout.expect(10).process();
+ expect(val).toEqual(123);
+ });
+ *
+ *
+ */
+angular.mock.createMockWindow = function() {
+ var mockWindow = {};
+ var setTimeoutQueue = [];
+
+ mockWindow.document = window.document;
+ mockWindow.getComputedStyle = angular.bind(window, window.getComputedStyle);
+ mockWindow.scrollTo = angular.bind(window, window.scrollTo);
+ mockWindow.navigator = window.navigator;
+ mockWindow.setTimeout = function(fn, delay) {
+ setTimeoutQueue.push({fn: fn, delay: delay});
+ };
+ mockWindow.setTimeout.queue = setTimeoutQueue;
+ mockWindow.setTimeout.expect = function(delay) {
+ if (setTimeoutQueue.length > 0) {
+ return {
+ process: function() {
+ var tick = setTimeoutQueue.shift();
+ expect(tick.delay).toEqual(delay);
+ tick.fn();
+ }
+ };
+ } else {
+ expect('SetTimoutQueue empty. Expecting delay of ').toEqual(delay);
+ }
+ };
+
+ return mockWindow;
+};
+
+/**
+ * @ngdoc function
+ * @name angular.mock.dump
+ * @description
+ *
+ * *NOTE*: this is not an injectable instance, just a globally available function.
+ *
+ * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging.
+ *
+ * This method is also available on window, where it can be used to display objects on debug console.
+ *
+ * @param {*} object - any object to turn into string.
+ * @return {string} a serialized string of the argument
+ */
+angular.mock.dump = function(object) {
+ return serialize(object);
+
+ function serialize(object) {
+ var out;
+
+ if (angular.isElement(object)) {
+ object = angular.element(object);
+ out = angular.element('');
+ angular.forEach(object, function(element) {
+ out.append(angular.element(element).clone());
+ });
+ out = out.html();
+ } else if (angular.isArray(object)) {
+ out = [];
+ angular.forEach(object, function(o) {
+ out.push(serialize(o));
+ });
+ out = '[ ' + out.join(', ') + ' ]';
+ } else if (angular.isObject(object)) {
+ if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) {
+ out = serializeScope(object);
+ } else if (object instanceof Error) {
+ out = object.stack || ('' + object.name + ': ' + object.message);
+ } else {
+ out = angular.toJson(object, true);
+ }
+ } else {
+ out = String(object);
+ }
+
+ return out;
+ }
+
+ function serializeScope(scope, offset) {
+ offset = offset || ' ';
+ var log = [offset + 'Scope(' + scope.$id + '): {'];
+ for ( var key in scope ) {
+ if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) {
+ log.push(' ' + key + ': ' + angular.toJson(scope[key]));
+ }
+ }
+ var child = scope.$$childHead;
+ while(child) {
+ log.push(serializeScope(child, offset + ' '));
+ child = child.$$nextSibling;
+ }
+ log.push('}');
+ return log.join('\n' + offset);
+ }
+};
+
+/**
+ * @ngdoc object
+ * @name ngMock.$httpBackend
+ * @description
+ * Fake HTTP backend implementation suitable for unit testing applications that use the
+ * {@link ng.$http $http service}.
+ *
+ * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less
+ * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
+ *
+ * During unit testing, we want our unit tests to run quickly and have no external dependencies so
+ * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or
+ * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is
+ * to verify whether a certain request has been sent or not, or alternatively just let the
+ * application make requests, respond with pre-trained responses and assert that the end result is
+ * what we expect it to be.
+ *
+ * This mock implementation can be used to respond with static or dynamic responses via the
+ * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
+ *
+ * When an Angular application needs some data from a server, it calls the $http service, which
+ * sends the request to a real server using $httpBackend service. With dependency injection, it is
+ * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
+ * the requests and respond with some testing data without sending a request to real server.
+ *
+ * There are two ways to specify what test data should be returned as http responses by the mock
+ * backend when the code under test makes http requests:
+ *
+ * - `$httpBackend.expect` - specifies a request expectation
+ * - `$httpBackend.when` - specifies a backend definition
+ *
+ *
+ * # Request Expectations vs Backend Definitions
+ *
+ * Request expectations provide a way to make assertions about requests made by the application and
+ * to define responses for those requests. The test will fail if the expected requests are not made
+ * or they are made in the wrong order.
+ *
+ * Backend definitions allow you to define a fake backend for your application which doesn't assert
+ * if a particular request was made or not, it just returns a trained response if a request is made.
+ * The test will pass whether or not the request gets made during testing.
+ *
+ *
+ *
+ *
Request expectations
Backend definitions
+ *
+ *
Syntax
+ *
.expect(...).respond(...)
+ *
.when(...).respond(...)
+ *
+ *
+ *
Typical usage
+ *
strict unit tests
+ *
loose (black-box) unit testing
+ *
+ *
+ *
Fulfills multiple requests
+ *
NO
+ *
YES
+ *
+ *
+ *
Order of requests matters
+ *
YES
+ *
NO
+ *
+ *
+ *
Request required
+ *
YES
+ *
NO
+ *
+ *
+ *
Response required
+ *
optional (see below)
+ *
YES
+ *
+ *
+ *
+ * In cases where both backend definitions and request expectations are specified during unit
+ * testing, the request expectations are evaluated first.
+ *
+ * If a request expectation has no response specified, the algorithm will search your backend
+ * definitions for an appropriate response.
+ *
+ * If a request didn't match any expectation or if the expectation doesn't have the response
+ * defined, the backend definitions are evaluated in sequential order to see if any of them match
+ * the request. The response from the first matched definition is returned.
+ *
+ *
+ * # Flushing HTTP requests
+ *
+ * The $httpBackend used in production, always responds to requests with responses asynchronously.
+ * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are
+ * hard to write, follow and maintain. At the same time the testing mock, can't respond
+ * synchronously because that would change the execution of the code under test. For this reason the
+ * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending
+ * requests and thus preserving the async api of the backend, while allowing the test to execute
+ * synchronously.
+ *
+ *
+ * # Unit testing with mock $httpBackend
+ *
+ *
+ // controller
+ function MyController($scope, $http) {
+ $http.get('/auth.py').success(function(data) {
+ $scope.user = data;
+ });
+
+ this.saveMessage = function(message) {
+ $scope.status = 'Saving...';
+ $http.post('/add-msg.py', message).success(function(response) {
+ $scope.status = '';
+ }).error(function() {
+ $scope.status = 'ERROR!';
+ });
+ };
+ }
+
+ // testing controller
+ var $httpBackend;
+
+ beforeEach(inject(function($injector) {
+ $httpBackend = $injector.get('$httpBackend');
+
+ // backend definition common for all tests
+ $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
+ }));
+
+
+ afterEach(function() {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+
+ it('should fetch authentication token', function() {
+ $httpBackend.expectGET('/auth.py');
+ var controller = scope.$new(MyController);
+ $httpBackend.flush();
+ });
+
+
+ it('should send msg to server', function() {
+ // now you don’t care about the authentication, but
+ // the controller will still send the request and
+ // $httpBackend will respond without you having to
+ // specify the expectation and response for this request
+ $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
+
+ var controller = scope.$new(MyController);
+ $httpBackend.flush();
+ controller.saveMessage('message content');
+ expect(controller.status).toBe('Saving...');
+ $httpBackend.flush();
+ expect(controller.status).toBe('');
+ });
+
+
+ it('should send auth header', function() {
+ $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
+ // check if the header was send, if it wasn't the expectation won't
+ // match the request and the test will fail
+ return headers['Authorization'] == 'xxx';
+ }).respond(201, '');
+
+ var controller = scope.$new(MyController);
+ controller.saveMessage('whatever');
+ $httpBackend.flush();
+ });
+
+ */
+angular.mock.$HttpBackendProvider = function() {
+ this.$get = ['$rootScope', createHttpBackendMock];
+};
+
+/**
+ * General factory function for $httpBackend mock.
+ * Returns instance for unit testing (when no arguments specified):
+ * - passing through is disabled
+ * - auto flushing is disabled
+ *
+ * Returns instance for e2e testing (when `$delegate` and `$browser` specified):
+ * - passing through (delegating request to real backend) is enabled
+ * - auto flushing is enabled
+ *
+ * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified)
+ * @param {Object=} $browser Auto-flushing enabled if specified
+ * @return {Object} Instance of $httpBackend mock
+ */
+function createHttpBackendMock($rootScope, $delegate, $browser) {
+ var definitions = [],
+ expectations = [],
+ responses = [],
+ responsesPush = angular.bind(responses, responses.push);
+
+ function createResponse(status, data, headers) {
+ if (angular.isFunction(status)) return status;
+
+ return function() {
+ return angular.isNumber(status)
+ ? [status, data, headers]
+ : [200, status, data];
+ };
+ }
+
+ // TODO(vojta): change params to: method, url, data, headers, callback
+ function $httpBackend(method, url, data, callback, headers, timeout) {
+ var xhr = new MockXhr(),
+ expectation = expectations[0],
+ wasExpected = false;
+
+ function prettyPrint(data) {
+ return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
+ ? data
+ : angular.toJson(data);
+ }
+
+ function wrapResponse(wrapped) {
+ if (!$browser && timeout && timeout.then) timeout.then(handleTimeout);
+
+ return handleResponse;
+
+ function handleResponse() {
+ var response = wrapped.response(method, url, data, headers);
+ xhr.$$respHeaders = response[2];
+ callback(response[0], response[1], xhr.getAllResponseHeaders());
+ }
+
+ function handleTimeout() {
+ for (var i = 0, ii = responses.length; i < ii; i++) {
+ if (responses[i] === handleResponse) {
+ responses.splice(i, 1);
+ callback(-1, undefined, '');
+ break;
+ }
+ }
+ }
+ }
+
+ if (expectation && expectation.match(method, url)) {
+ if (!expectation.matchData(data))
+ throw Error('Expected ' + expectation + ' with different data\n' +
+ 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
+
+ if (!expectation.matchHeaders(headers))
+ throw Error('Expected ' + expectation + ' with different headers\n' +
+ 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
+ prettyPrint(headers));
+
+ expectations.shift();
+
+ if (expectation.response) {
+ responses.push(wrapResponse(expectation));
+ return;
+ }
+ wasExpected = true;
+ }
+
+ var i = -1, definition;
+ while ((definition = definitions[++i])) {
+ if (definition.match(method, url, data, headers || {})) {
+ if (definition.response) {
+ // if $browser specified, we do auto flush all requests
+ ($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
+ } else if (definition.passThrough) {
+ $delegate(method, url, data, callback, headers, timeout);
+ } else throw Error('No response defined !');
+ return;
+ }
+ }
+ throw wasExpected ?
+ Error('No response defined !') :
+ Error('Unexpected request: ' + method + ' ' + url + '\n' +
+ (expectation ? 'Expected ' + expectation : 'No more request expected'));
+ }
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#when
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition.
+ *
+ * @param {string} method HTTP method.
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ *
+ * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
+ * – The respond method takes a set of static data to be returned or a function that can return
+ * an array containing response status (number), response data (string) and response headers
+ * (Object).
+ */
+ $httpBackend.when = function(method, url, data, headers) {
+ var definition = new MockHttpExpectation(method, url, data, headers),
+ chain = {
+ respond: function(status, data, headers) {
+ definition.response = createResponse(status, data, headers);
+ }
+ };
+
+ if ($browser) {
+ chain.passThrough = function() {
+ definition.passThrough = true;
+ };
+ }
+
+ definitions.push(definition);
+ return chain;
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenGET
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for GET requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenHEAD
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for HEAD requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenDELETE
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for DELETE requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenPOST
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for POST requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenPUT
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for PUT requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenJSONP
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for JSONP requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+ createShortMethods('when');
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expect
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation.
+ *
+ * @param {string} method HTTP method.
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current expectation.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ *
+ * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
+ * – The respond method takes a set of static data to be returned or a function that can return
+ * an array containing response status (number), response data (string) and response headers
+ * (Object).
+ */
+ $httpBackend.expect = function(method, url, data, headers) {
+ var expectation = new MockHttpExpectation(method, url, data, headers);
+ expectations.push(expectation);
+ return {
+ respond: function(status, data, headers) {
+ expectation.response = createResponse(status, data, headers);
+ }
+ };
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectGET
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for GET requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled. See #expect for more info.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectHEAD
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for HEAD requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectDELETE
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for DELETE requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectPOST
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for POST requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectPUT
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for PUT requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectPATCH
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for PATCH requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectJSONP
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for JSONP requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+ createShortMethods('expect');
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#flush
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Flushes all pending requests using the trained responses.
+ *
+ * @param {number=} count Number of responses to flush (in the order they arrived). If undefined,
+ * all pending requests will be flushed. If there are no pending requests when the flush method
+ * is called an exception is thrown (as this typically a sign of programming error).
+ */
+ $httpBackend.flush = function(count) {
+ $rootScope.$digest();
+ if (!responses.length) throw Error('No pending request to flush !');
+
+ if (angular.isDefined(count)) {
+ while (count--) {
+ if (!responses.length) throw Error('No more pending request to flush !');
+ responses.shift()();
+ }
+ } else {
+ while (responses.length) {
+ responses.shift()();
+ }
+ }
+ $httpBackend.verifyNoOutstandingExpectation();
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#verifyNoOutstandingExpectation
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Verifies that all of the requests defined via the `expect` api were made. If any of the
+ * requests were not made, verifyNoOutstandingExpectation throws an exception.
+ *
+ * Typically, you would call this method following each test case that asserts requests using an
+ * "afterEach" clause.
+ *
+ *
+ */
+ $httpBackend.verifyNoOutstandingExpectation = function() {
+ $rootScope.$digest();
+ if (expectations.length) {
+ throw Error('Unsatisfied requests: ' + expectations.join(', '));
+ }
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#verifyNoOutstandingRequest
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Verifies that there are no outstanding requests that need to be flushed.
+ *
+ * Typically, you would call this method following each test case that asserts requests using an
+ * "afterEach" clause.
+ *
+ *
+ */
+ $httpBackend.verifyNoOutstandingRequest = function() {
+ if (responses.length) {
+ throw Error('Unflushed requests: ' + responses.length);
+ }
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#resetExpectations
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Resets all request expectations, but preserves all backend definitions. Typically, you would
+ * call resetExpectations during a multiple-phase test when you want to reuse the same instance of
+ * $httpBackend mock.
+ */
+ $httpBackend.resetExpectations = function() {
+ expectations.length = 0;
+ responses.length = 0;
+ };
+
+ return $httpBackend;
+
+
+ function createShortMethods(prefix) {
+ angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) {
+ $httpBackend[prefix + method] = function(url, headers) {
+ return $httpBackend[prefix](method, url, undefined, headers)
+ }
+ });
+
+ angular.forEach(['PUT', 'POST', 'PATCH'], function(method) {
+ $httpBackend[prefix + method] = function(url, data, headers) {
+ return $httpBackend[prefix](method, url, data, headers)
+ }
+ });
+ }
+}
+
+function MockHttpExpectation(method, url, data, headers) {
+
+ this.data = data;
+ this.headers = headers;
+
+ this.match = function(m, u, d, h) {
+ if (method != m) return false;
+ if (!this.matchUrl(u)) return false;
+ if (angular.isDefined(d) && !this.matchData(d)) return false;
+ if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
+ return true;
+ };
+
+ this.matchUrl = function(u) {
+ if (!url) return true;
+ if (angular.isFunction(url.test)) return url.test(u);
+ return url == u;
+ };
+
+ this.matchHeaders = function(h) {
+ if (angular.isUndefined(headers)) return true;
+ if (angular.isFunction(headers)) return headers(h);
+ return angular.equals(headers, h);
+ };
+
+ this.matchData = function(d) {
+ if (angular.isUndefined(data)) return true;
+ if (data && angular.isFunction(data.test)) return data.test(d);
+ if (data && !angular.isString(data)) return angular.toJson(data) == d;
+ return data == d;
+ };
+
+ this.toString = function() {
+ return method + ' ' + url;
+ };
+}
+
+function MockXhr() {
+
+ // hack for testing $http, $httpBackend
+ MockXhr.$$lastInstance = this;
+
+ this.open = function(method, url, async) {
+ this.$$method = method;
+ this.$$url = url;
+ this.$$async = async;
+ this.$$reqHeaders = {};
+ this.$$respHeaders = {};
+ };
+
+ this.send = function(data) {
+ this.$$data = data;
+ };
+
+ this.setRequestHeader = function(key, value) {
+ this.$$reqHeaders[key] = value;
+ };
+
+ this.getResponseHeader = function(name) {
+ // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last
+ var header = this.$$respHeaders[name];
+ if (header) return header;
+
+ name = angular.lowercase(name);
+ header = this.$$respHeaders[name];
+ if (header) return header;
+
+ header = undefined;
+ angular.forEach(this.$$respHeaders, function(headerVal, headerName) {
+ if (!header && angular.lowercase(headerName) == name) header = headerVal;
+ });
+ return header;
+ };
+
+ this.getAllResponseHeaders = function() {
+ var lines = [];
+
+ angular.forEach(this.$$respHeaders, function(value, key) {
+ lines.push(key + ': ' + value);
+ });
+ return lines.join('\n');
+ };
+
+ this.abort = angular.noop;
+}
+
+
+/**
+ * @ngdoc function
+ * @name ngMock.$timeout
+ * @description
+ *
+ * This service is just a simple decorator for {@link ng.$timeout $timeout} service
+ * that adds a "flush" and "verifyNoPendingTasks" methods.
+ */
+
+angular.mock.$TimeoutDecorator = function($delegate, $browser) {
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$timeout#flush
+ * @methodOf ngMock.$timeout
+ * @description
+ *
+ * Flushes the queue of pending tasks.
+ */
+ $delegate.flush = function() {
+ $browser.defer.flush();
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$timeout#verifyNoPendingTasks
+ * @methodOf ngMock.$timeout
+ * @description
+ *
+ * Verifies that there are no pending tasks that need to be flushed.
+ */
+ $delegate.verifyNoPendingTasks = function() {
+ if ($browser.deferredFns.length) {
+ throw Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
+ formatPendingTasksAsString($browser.deferredFns));
+ }
+ };
+
+ function formatPendingTasksAsString(tasks) {
+ var result = [];
+ angular.forEach(tasks, function(task) {
+ result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}');
+ });
+
+ return result.join(', ');
+ }
+
+ return $delegate;
+};
+
+/**
+ *
+ */
+angular.mock.$RootElementProvider = function() {
+ this.$get = function() {
+ return angular.element('');
+ }
+};
+
+/**
+ * @ngdoc overview
+ * @name ngMock
+ * @description
+ *
+ * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful
+ * mocks to the {@link AUTO.$injector $injector}.
+ */
+angular.module('ngMock', ['ng']).provider({
+ $browser: angular.mock.$BrowserProvider,
+ $exceptionHandler: angular.mock.$ExceptionHandlerProvider,
+ $log: angular.mock.$LogProvider,
+ $httpBackend: angular.mock.$HttpBackendProvider,
+ $rootElement: angular.mock.$RootElementProvider
+}).config(function($provide) {
+ $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
+});
+
+/**
+ * @ngdoc overview
+ * @name ngMockE2E
+ * @description
+ *
+ * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing.
+ * Currently there is only one mock present in this module -
+ * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
+ */
+angular.module('ngMockE2E', ['ng']).config(function($provide) {
+ $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
+});
+
+/**
+ * @ngdoc object
+ * @name ngMockE2E.$httpBackend
+ * @description
+ * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of
+ * applications that use the {@link ng.$http $http service}.
+ *
+ * *Note*: For fake http backend implementation suitable for unit testing please see
+ * {@link ngMock.$httpBackend unit-testing $httpBackend mock}.
+ *
+ * This implementation can be used to respond with static or dynamic responses via the `when` api
+ * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the
+ * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch
+ * templates from a webserver).
+ *
+ * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application
+ * is being developed with the real backend api replaced with a mock, it is often desirable for
+ * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch
+ * templates or static files from the webserver). To configure the backend with this behavior
+ * use the `passThrough` request handler of `when` instead of `respond`.
+ *
+ * Additionally, we don't want to manually have to flush mocked out requests like we do during unit
+ * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests
+ * automatically, closely simulating the behavior of the XMLHttpRequest object.
+ *
+ * To setup the application to run with this http backend, you have to create a module that depends
+ * on the `ngMockE2E` and your application modules and defines the fake backend:
+ *
+ *
+ * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
+ * myAppDev.run(function($httpBackend) {
+ * phones = [{name: 'phone1'}, {name: 'phone2'}];
+ *
+ * // returns the current list of phones
+ * $httpBackend.whenGET('/phones').respond(phones);
+ *
+ * // adds a new phone to the phones array
+ * $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
+ * phones.push(angular.fromJSON(data));
+ * });
+ * $httpBackend.whenGET(/^\/templates\//).passThrough();
+ * //...
+ * });
+ *
+ *
+ * Afterwards, bootstrap your app with this new module.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#when
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition.
+ *
+ * @param {string} method HTTP method.
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ *
+ * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
+ * – The respond method takes a set of static data to be returned or a function that can return
+ * an array containing response status (number), response data (string) and response headers
+ * (Object).
+ * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough`
+ * handler, will be pass through to the real backend (an XHR request will be made to the
+ * server.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenGET
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for GET requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenHEAD
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for HEAD requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenDELETE
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for DELETE requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenPOST
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for POST requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenPUT
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for PUT requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenPATCH
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for PATCH requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenJSONP
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for JSONP requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+angular.mock.e2e = {};
+angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock];
+
+
+angular.mock.clearDataCache = function() {
+ var key,
+ cache = angular.element.cache;
+
+ for(key in cache) {
+ if (cache.hasOwnProperty(key)) {
+ var handle = cache[key].handle;
+
+ handle && angular.element(handle.elem).unbind();
+ delete cache[key];
+ }
+ }
+};
+
+
+window.jstestdriver && (function(window) {
+ /**
+ * Global method to output any number of objects into JSTD console. Useful for debugging.
+ */
+ window.dump = function() {
+ var args = [];
+ angular.forEach(arguments, function(arg) {
+ args.push(angular.mock.dump(arg));
+ });
+ jstestdriver.console.log.apply(jstestdriver.console, args);
+ if (window.console) {
+ window.console.log.apply(window.console, args);
+ }
+ };
+})(window);
+
+
+(window.jasmine || window.mocha) && (function(window) {
+
+ var currentSpec = null;
+
+ beforeEach(function() {
+ currentSpec = this;
+ });
+
+ afterEach(function() {
+ var injector = currentSpec.$injector;
+
+ currentSpec.$injector = null;
+ currentSpec.$modules = null;
+ currentSpec = null;
+
+ if (injector) {
+ injector.get('$rootElement').unbind();
+ injector.get('$browser').pollFns.length = 0;
+ }
+
+ angular.mock.clearDataCache();
+
+ // clean up jquery's fragment cache
+ angular.forEach(angular.element.fragments, function(val, key) {
+ delete angular.element.fragments[key];
+ });
+
+ MockXhr.$$lastInstance = null;
+
+ angular.forEach(angular.callbacks, function(val, key) {
+ delete angular.callbacks[key];
+ });
+ angular.callbacks.counter = 0;
+ });
+
+ function isSpecRunning() {
+ return currentSpec && (window.mocha || currentSpec.queue.running);
+ }
+
+ /**
+ * @ngdoc function
+ * @name angular.mock.module
+ * @description
+ *
+ * *NOTE*: This function is also published on window for easy access.
+ *
+ * This function registers a module configuration code. It collects the configuration information
+ * which will be used when the injector is created by {@link angular.mock.inject inject}.
+ *
+ * See {@link angular.mock.inject inject} for usage example
+ *
+ * @param {...(string|Function)} fns any number of modules which are represented as string
+ * aliases or as anonymous module initialization functions. The modules are used to
+ * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded.
+ */
+ window.module = angular.mock.module = function() {
+ var moduleFns = Array.prototype.slice.call(arguments, 0);
+ return isSpecRunning() ? workFn() : workFn;
+ /////////////////////
+ function workFn() {
+ if (currentSpec.$injector) {
+ throw Error('Injector already created, can not register a module!');
+ } else {
+ var modules = currentSpec.$modules || (currentSpec.$modules = []);
+ angular.forEach(moduleFns, function(module) {
+ modules.push(module);
+ });
+ }
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name angular.mock.inject
+ * @description
+ *
+ * *NOTE*: This function is also published on window for easy access.
+ *
+ * The inject function wraps a function into an injectable function. The inject() creates new
+ * instance of {@link AUTO.$injector $injector} per test, which is then used for
+ * resolving references.
+ *
+ * See also {@link angular.mock.module module}
+ *
+ * Example of what a typical jasmine tests looks like with the inject method.
+ *
+ *
+ * angular.module('myApplicationModule', [])
+ * .value('mode', 'app')
+ * .value('version', 'v1.0.1');
+ *
+ *
+ * describe('MyApp', function() {
+ *
+ * // You need to load modules that you want to test,
+ * // it loads only the "ng" module by default.
+ * beforeEach(module('myApplicationModule'));
+ *
+ *
+ * // inject() is used to inject arguments of all given functions
+ * it('should provide a version', inject(function(mode, version) {
+ * expect(version).toEqual('v1.0.1');
+ * expect(mode).toEqual('app');
+ * }));
+ *
+ *
+ * // The inject and module method can also be used inside of the it or beforeEach
+ * it('should override a version and test the new version is injected', function() {
+ * // module() takes functions or strings (module aliases)
+ * module(function($provide) {
+ * $provide.value('version', 'overridden'); // override version here
+ * });
+ *
+ * inject(function(version) {
+ * expect(version).toEqual('overridden');
+ * });
+ * ));
+ * });
+ *
+ *
+ *
+ * @param {...Function} fns any number of functions which will be injected using the injector.
+ */
+ window.inject = angular.mock.inject = function() {
+ var blockFns = Array.prototype.slice.call(arguments, 0);
+ var errorForStack = new Error('Declaration Location');
+ return isSpecRunning() ? workFn() : workFn;
+ /////////////////////
+ function workFn() {
+ var modules = currentSpec.$modules || [];
+
+ modules.unshift('ngMock');
+ modules.unshift('ng');
+ var injector = currentSpec.$injector;
+ if (!injector) {
+ injector = currentSpec.$injector = angular.injector(modules);
+ }
+ for(var i = 0, ii = blockFns.length; i < ii; i++) {
+ try {
+ injector.invoke(blockFns[i] || angular.noop, this);
+ } catch (e) {
+ if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack;
+ throw e;
+ } finally {
+ errorForStack = null;
+ }
+ }
+ }
+ };
+})(window);
diff --git a/WebCms/Umbraco/lib/angular/1.1.5/angular-resource.js b/WebCms/Umbraco/lib/angular/1.1.5/angular-resource.js
new file mode 100644
index 0000000..acaa84c
--- /dev/null
+++ b/WebCms/Umbraco/lib/angular/1.1.5/angular-resource.js
@@ -0,0 +1,537 @@
+/**
+ * @license AngularJS v1.1.5
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular, undefined) {
+'use strict';
+
+/**
+ * @ngdoc overview
+ * @name ngResource
+ * @description
+ */
+
+/**
+ * @ngdoc object
+ * @name ngResource.$resource
+ * @requires $http
+ *
+ * @description
+ * A factory which creates a resource object that lets you interact with
+ * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
+ *
+ * The returned resource object has action methods which provide high-level behaviors without
+ * the need to interact with the low level {@link ng.$http $http} service.
+ *
+ * # Installation
+ * To use $resource make sure you have included the `angular-resource.js` that comes in Angular
+ * package. You can also find this file on Google CDN, bower as well as at
+ * {@link http://code.angularjs.org/ code.angularjs.org}.
+ *
+ * Finally load the module in your application:
+ *
+ * angular.module('app', ['ngResource']);
+ *
+ * and you are ready to get started!
+ *
+ * @param {string} url A parametrized URL template with parameters prefixed by `:` as in
+ * `/user/:username`. If you are using a URL with a port number (e.g.
+ * `http://example.com:8080/api`), you'll need to escape the colon character before the port
+ * number, like this: `$resource('http://example.com\\:8080/api')`.
+ *
+ * If you are using a url with a suffix, just add the suffix, like this:
+ * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')
+ * or even `$resource('http://example.com/resource/:resource_id.:format')`
+ * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
+ * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
+ * can escape it with `/\.`.
+ *
+ * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
+ * `actions` methods. If any of the parameter value is a function, it will be executed every time
+ * when a param value needs to be obtained for a request (unless the param was overridden).
+ *
+ * Each key value in the parameter object is first bound to url template if present and then any
+ * excess keys are appended to the url search query after the `?`.
+ *
+ * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
+ * URL `/path/greet?salutation=Hello`.
+ *
+ * If the parameter value is prefixed with `@` then the value of that parameter is extracted from
+ * the data object (useful for non-GET operations).
+ *
+ * @param {Object.