Improves notification system

* ICalendarManager.cs: WIP booking TODO a calendar provider

* NpgsqlProfileProvider.cs: Fixes the defaultValue specification from
  config file

* BlogsController.cs:
* AccountController.cs:
* CalendarController.cs: refactoring : the Yavsc controller name

* instdbws.sql: a new profile value : a boolean, `AllowCookies` :'{

* style.css: a class to display notification

* HomeController.cs: Notifies users this site uses cookies (what for
  an information!)
If authenticated, at dimissing this notification, the user's profile
  is updated,
and he'll not mess up anymore with the info.

* App.master:
* YavscHelpers.cs: adds usage of click_action value at
displaying a notification.

* yavsc.js: Implements the notification `click_action`

* Web.config: * enables anonymous profiles
* adds a new `allowcookies` profile property

* Web.csproj: Yavsc controller refactoring

* YaEvent.cs:
* IFreeDateSet.cs: WIP booking

* LocalizedText.resx:
* LocalizedText.fr.resx:
* LocalizedText.Designer.cs:
* LocalizedText.fr.Designer.cs: implements the message "uses cookies"

* YavscModel.csproj: refactoring

* Notification.cs: The Yavsc otification will start as a Google one
  ...
many properties are not yet used, but all seems usefull.

* Web.config: code prettying

* YavscController.cs: Gives Yavsc a concrete base controller
vnext
Paul Schneider 9 years ago
parent 0e6da27309
commit b8446c2d3e
26 changed files with 256 additions and 116 deletions

@ -0,0 +1,5 @@
2015-10-28 Paul Schneider <paul@pschneider.fr>
* NpgsqlProfileProvider.cs: Fixes the defaultValue
specification from config file

@ -221,7 +221,7 @@ namespace Npgsql.Web
} else {
foreach (SettingsProperty p in collection) {
SettingsPropertyValue v = new SettingsPropertyValue (p);
v.PropertyValue = null;
v.PropertyValue = p.DefaultValue;
c.Add (v);
}
}

@ -32,7 +32,7 @@ namespace Yavsc.ApiControllers
/// <summary>
/// Account controller.
/// </summary>
public class AccountController : YavscApiController
public class AccountController : YavscController
{
/// <summary>

@ -19,7 +19,7 @@ namespace Yavsc.ApiControllers
/// <summary>
/// Blogs API controller.
/// </summary>
public class BlogsController : YavscApiController
public class BlogsController : YavscController
{
/// <summary>
/// Tag the specified model.

@ -50,7 +50,7 @@ namespace Yavsc.ApiControllers
},
new YaEvent () {
Title = "Test2",
ImgLocator = "http://bla/im.png",
Photo = "http://bla/im.png",
Location = new Position () {
Longitude = 0,
Latitude = 0
@ -66,7 +66,7 @@ namespace Yavsc.ApiControllers
},
new YaEvent () {
Title = "Test2",
ImgLocator = "http://bla/im.png",
Photo = "http://bla/im.png",
Location = new Position () {
Longitude = 0,
Latitude = 0

@ -21,14 +21,26 @@
using System;
using System.Web.Http;
using System.Net.Http;
using System.Web.Profile;
namespace Yavsc.ApiControllers
{
public abstract class YavscApiController : ApiController
public class YavscController : ApiController
{
public YavscApiController ()
public YavscController ()
{
}
public class Auth {
public string Id { get; set; }
}
public void AllowCookies (Auth model)
{
if (model.Id != null) {
ProfileBase anonymousProfile = ProfileBase.Create (model.Id);
anonymousProfile.SetPropertyValue ("allowcookies", true);
}
}
protected HttpResponseMessage DefaultResponse()
{

@ -91,13 +91,13 @@ WITH (
OIDS=FALSE
);
-- Table: profiledata
-- Table: profiledata
-- DROP TABLE profiledata;
CREATE TABLE profiledata
(
uniqueid integer,
uniqueid integer,
zipcode character varying(10),
cityandstate character varying(255),
blogtitle character varying(255), -- Blog Title
@ -122,6 +122,7 @@ CREATE TABLE profiledata
avatar character varying(512), -- url for an avatar
gcalapi boolean NOT NULL DEFAULT false,
gregid character varying(1024), -- Google Cloud Message registration identifier
allowcookies boolean NOT NULL DEFAULT false,
CONSTRAINT fkprofiles2 FOREIGN KEY (uniqueid)
REFERENCES profiles (uniqueid) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
@ -129,18 +130,18 @@ CREATE TABLE profiledata
WITH (
OIDS=FALSE
);
COMMENT ON COLUMN profiledata."address.address" IS 'Postal address';
COMMENT ON COLUMN profiledata.blogtitle IS 'Blog Title';
COMMENT ON COLUMN profiledata.address IS 'Postal address';
COMMENT ON COLUMN profiledata.accountnumber IS 'Numero de compte';
COMMENT ON COLUMN profiledata.bankedkey IS 'clé RIB';
COMMENT ON COLUMN profiledata.bankcode IS 'Code banque';
COMMENT ON COLUMN profiledata.gtoken IS 'Google authentification token';
COMMENT ON COLUMN profiledata.grefreshtoken IS 'Google refresh token';
COMMENT ON COLUMN profiledata.gtokentype IS 'Google access token type';
COMMENT ON COLUMN profiledata.gcalid IS 'Google calendar identifier';
COMMENT ON COLUMN profiledata.gtokenexpir IS 'Google access token expiration date';
COMMENT ON COLUMN profiledata.avatar IS 'url for an avatar';
COMMENT ON COLUMN profiledata."bank.accountnumber" IS 'Numero de compte';
COMMENT ON COLUMN profiledata."bank.key" IS 'clé RIB';
COMMENT ON COLUMN profiledata."bank.code" IS 'Code banque';
COMMENT ON COLUMN profiledata."google.token" IS 'Google authentification token';
COMMENT ON COLUMN profiledata."google.refreshtoken" IS 'Google refresh token';
COMMENT ON COLUMN profiledata."google.tokentype" IS 'Google access token type';
COMMENT ON COLUMN profiledata."google.calid" IS 'Google calendar identifier';
COMMENT ON COLUMN profiledata."google.tokenexpir" IS 'Google access token expiration date';
COMMENT ON COLUMN profiledata."google.regid" IS 'Google Cloud Message registration identifier';
COMMENT ON COLUMN profiledata.gregid IS 'Google Cloud Message registration identifier';
-- Index: fki_fkprofiles2
@ -154,6 +155,9 @@ CREATE INDEX fki_fkprofiles2
-- Table: profiles

@ -27,6 +27,16 @@ body {
url('/images/FhHRx.gif')
50% 50%
no-repeat;
overflow: scroll;
}
.dispmodal {
position: fixed;
z-index: 1000;
top: 0;
left: 0;
height: 100%;
width: 100%;
overflow: scroll;
}
body.loading {
@ -217,6 +227,10 @@ label {
font-size: large;
background-color: rgba(64,64,0,0.5);
border: solid green 1px;
padding: 1em;
border-radius:1em;
margin:1em;
padding:1em;
}
.dirty {
background-color: rgba(128,128,0,0.5);
@ -356,6 +370,9 @@ header h1, header a , .actionlink, .menuitem, a { padding:.5em;}
}
#notifications {
padding: .5em;
border-radius:.5em;
margin:.5em;
padding:.5em;
}
.menuitem {
display: block;

@ -1,3 +1,38 @@
2015-10-28 Paul Schneider <paul@pschneider.fr>
* BlogsController.cs:
* AccountController.cs:
* CalendarController.cs: refactoring : the Yavsc controller
name
* instdbws.sql: a new profile value : a boolean,
`AllowCookies` :'{
* style.css: a class to display notification
* HomeController.cs: Notifies users this site uses cookies
(what for an information!)
If authenticated, at dimissing this notification, the user's
profile is updated,
and he'll not mess up anymore with the info.
* App.master:
* YavscHelpers.cs: adds usage of click_action value at
displaying a notification.
* yavsc.js: Implements the notification `click_action`
* Web.config: * enables anonymous profiles
* adds a new `allowcookies` profile property
* Web.csproj: Yavsc controller refactoring
* Web.config: code prettying
* YavscController.cs: Gives Yavsc a concrete base controller
2015-10-27 Paul Schneider <paul@pschneider.fr>
* AppAdmin.master: A new master page for Administration

@ -14,6 +14,8 @@ using Yavsc.Helpers;
using Yavsc;
using System.Web.Mvc;
using Yavsc.Model.Blogs;
using System.Web.Security;
using System.Web.Profile;
namespace Yavsc.Controllers
{
@ -85,6 +87,17 @@ namespace Yavsc.Controllers
/// </summary>
public ActionResult Index ()
{
var anonid = Request.AnonymousID;
if (Session.IsNewSession) {
if (!Request.IsAuthenticated) {
ProfileBase anonymousProfile = ProfileBase.Create(anonid);
object ac = anonymousProfile.GetPropertyValue ("allowcookies");
if (ac is string && ac!="true")
YavscHelpers.Notify (ViewData, LocalizedText.ThisSiteUsesCookies,
"function(){Yavsc.ajax(\"/Yavsc/AllowCookies\", { id:'"+anonid+"' });}");
}
}
foreach (string tagname in new string[] {"Accueil","Événements","Mentions légales"})
{
TagInfo ti = BlogManager.GetTagInfo (tagname);

@ -15,10 +15,12 @@ using System.Web.Profile;
using System.Web.Script.Serialization;
using System.Web.Mvc;
using System.Text.RegularExpressions;
using Yavsc.Model.Messaging;
namespace Yavsc.Helpers
{
/// <summary>
/// Yavsc helpers.
/// </summary>
@ -219,10 +221,11 @@ namespace Yavsc.Helpers
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Serialize(obj);
}
/// <summary>
/// Notifies
/// Notify the specified helper and message.
/// </summary>
/// <param name="ViewData">View data.</param>
/// <param name="helper">Helper.</param>
/// <param name="message">Message.</param>
public static void Notify (this HtmlHelper helper, string message) {
Notify (helper.ViewData, message);
@ -232,10 +235,12 @@ namespace Yavsc.Helpers
/// </summary>
/// <param name="viewData">View data.</param>
/// <param name="message">Message.</param>
public static void Notify(ViewDataDictionary viewData, string message) {
public static void Notify(ViewDataDictionary viewData, string message, string click_action=null) {
if (viewData ["Notifications"] == null)
viewData ["Notifications"] = new List<string> ();
(viewData ["Notifications"] as List<string>).Add (message.Replace("\'","\\\'"));
viewData ["Notifications"] = new List<Notification> ();
(viewData ["Notifications"] as List<Notification>).Add (
new Notification { body = message.Replace("\'","\\\'"),
click_action = click_action } ) ;
}
/// <summary>
/// Files the list.

@ -36,11 +36,12 @@ var apiBaseUrl = '<%=Url.Content(Yavsc.WebApiConfig.UrlPrefixRelative)%>';
</asp:ContentPlaceHolder>
<asp:ContentPlaceHolder ID="header" runat="server"></asp:ContentPlaceHolder>
<div id="notifications"></div>
<%if (ViewData ["Notifications"]!=null) { %>
<% if (ViewData ["Notifications"]!=null) { %>
<script>
$(document).ready(function(){
<% foreach (string note in (IEnumerable<string>) ViewData ["Notifications"] ) { %>
Yavsc.notice('<%=note%>');
<% foreach (Notification note in (IEnumerable<Notification>) ViewData ["Notifications"] ) {
if (note.click_action == null) {%> Yavsc.notice('<%=note.body%>'); <% }
else {%> Yavsc.notice('<%=note.body%>', <%=note.click_action%>); <% } %>
<% } %>
});
</script>

@ -1,8 +1,8 @@
var Yavsc = (function(apiBaseUrl){
var self = {};
var $notifications = $('#notifications');
function dumpprops(obj) {
self.dumpprops = function (obj) {
var str = "";
for(var k in obj)
if (obj.hasOwnProperty(k))
@ -30,32 +30,42 @@ self.dimiss = function () {
$(this).parent().remove();
};
self.ajax = function (method,data,callback) {
$.ajax({
url: self.apiBaseUrl+method,
type: "POST",
data: data,
success: function (response) {
if (callback) callback(response);
},
statusCode: {
400: Yavsc.onAjaxBadInput
},
error: Yavsc.onAjaxError});
};
self.onScroll = function() {
var $notifications = $('#notifications');
if ($notifications.has('*').length>0) {
if ($(window).scrollTop()>375) {
console.log('fixit');
$notifications.css('position','fixed');
$notifications.css('z-index',2);
$notifications.css('top',0);
if ($(window).scrollTop()>100) {
$notifications.addClass("dispmodal");
}
else {
$notifications.css('position','static');
$notifications.css('z-index',1);
$notifications.removeClass("dispmodal");
}}
};
self.notice = function (msg, msgok) {
self.notice = function (msg, callback, msgok) {
if (!msgok) msgok='Ok';
if (msg) {
var note = $('<div class="notification">'+msg+'<br></div>');
$('<a class="actionlink"><i class="fa fa-check">'+msgok+'</i></a>').click(self.dimiss).appendTo(note);
var btn = $('<a class="actionlink"><i class="fa fa-check">'+msgok+'</i></a>');
if (callback) btn.click(callback);
btn.click(self.dimiss).appendTo(note);
note.appendTo("#notifications");
self.onScroll();
}
};
};
self.onAjaxBadInput = function (data)
self.onAjaxBadInput = function (data)
{
if (!data) { Yavsc.notice('no data'); return; }
if (!data.responseJSON) { Yavsc.notice('no json data:'+data); return; }
@ -68,7 +78,6 @@ self.notice = function (msg, msgok) {
else
errspan.innerHTML=value.errors.join("<br/>");
});
};
self.onAjaxError = function (xhr, ajaxOptions, thrownError) {
@ -95,24 +104,16 @@ $btnshow.addClass('hidden');
$btnhide.removeClass('hidden');
});
});
});
$(document).ready(function(){
$body = $("body");
$(document).on({
ajaxStart: function() { $body.addClass("loading"); },
ajaxStop: function() { $body.removeClass("loading"); }
});
$body = $("body");
$(document).on({
ajaxStart: function() { $body.addClass("loading"); },
ajaxStop: function() { $body.removeClass("loading"); }
});
$(window).scroll(self.onScroll);
});
return self;
})();

@ -29,7 +29,6 @@
<add namespace="Yavsc.Model.Circles" />
<add namespace="Yavsc.Model.Calendar" />
<add namespace="System.Collections.Generic" />
</namespaces>
</pages>
</system.web>

@ -68,11 +68,13 @@ http://msdn2.microsoft.com/en-us/library/b5ysx397.aspx
<add namespace="System.Collections.Generic" />
<add namespace="Yavsc.Helpers" />
<add namespace="Yavsc.Model" />
<add namespace="Yavsc.Model.Messaging" />
</namespaces>
</pages>
<authorization>
<allow users="*" />
</authorization>
<anonymousIdentification enabled="true" />
<httpHandlers>
<remove verb="*" path="*.asmx" />
<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
@ -139,6 +141,7 @@ http://msdn2.microsoft.com/en-us/library/b5ysx397.aspx
<add name="gcalapi" />
<add name="gcalid" />
<add name="gregid" />
<add name="allowcookies" type="System.Boolean" allowAnonymous="true" defaultValue="false"/>
</properties>
</profile>
<blog defaultProvider="NpgsqlBlogProvider">

@ -205,7 +205,7 @@
<Compile Include="Helpers\Google\PeopleApi.cs" />
<Compile Include="ApiControllers\PaypalController.cs" />
<Compile Include="ApiControllers\AuthorizationDenied.cs" />
<Compile Include="ApiControllers\YavscApiController.cs" />
<Compile Include="ApiControllers\YavscController.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Web.config" />

@ -0,0 +1,32 @@
//
// ICalendarManager.cs
//
// Author:
// Paul Schneider <paul@pschneider.fr>
//
// Copyright (c) 2015 GNU GPL
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using Yavsc.Model.Google;
using System.Collections.Generic;
namespace Yavsc.Model.Calendar
{
public interface ICalendarManager {
IFreeDateSet GetFreeDates(string username, BookQuery req);
bool Book(string username, YaEvent ev);
}
}

@ -19,40 +19,36 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using Yavsc.Model.Google;
using System.Collections.Generic;
namespace Yavsc.Model.Calendar
{
/// <summary>
/// Free date.
/// </summary>
public class FreeDateSet
public interface IFreeDateSet
{
/// <summary>
/// Gets or sets the reference.
/// </summary>
/// <value>The reference.</value>
public DateTime [] Values { get; set; }
IEnumerable<Period> Values { get; set; }
/// <summary>
/// Gets or sets the duration.
/// </summary>
/// <value>The duration.</value>
public TimeSpan Duration { get; set; }
TimeSpan Duration { get; set; }
/// <summary>
/// Gets or sets the attendees.
/// </summary>
/// <value>The attendees.</value>
public string UserName { get; set; }
string UserName { get; set; }
/// <summary>
/// Gets or sets the location.
/// </summary>
/// <value>The location.</value>
public string Location { get; set; }
static FreeDateSet CreateFromCalAndQuery(CalendarEventList cal, BookQuery query)
{
throw new NotImplementedException ();
}
string Location { get; set; }
}
}

@ -26,13 +26,8 @@ using Yavsc.Model;
namespace Yavsc.Model.Calendar
{
/// <summary>
/// NF event.
/// </summary>
public class YaEvent
public class BaseEvent
{
/// <summary>
/// The title.
/// </summary>
@ -45,6 +40,7 @@ namespace Yavsc.Model.Calendar
[Required(ErrorMessage="Please, choose a Description.")]
[Display(ResourceType=typeof(LocalizedText),Name="Description")]
public string Description { get; set; }
/// <summary>
/// The location.
/// </summary>
@ -65,6 +61,13 @@ namespace Yavsc.Model.Calendar
[Required(ErrorMessage="Please, choose an End Date.")]
[Display(ResourceType=typeof(LocalizedText),Name="EndDate")]
public DateTime EndDate { get; set; }
}
/// <summary>
/// NF event.
/// </summary>
public class YaEvent : BaseEvent
{
/// <summary>
/// The name of the NF provider.
/// </summary>
@ -90,7 +93,7 @@ namespace Yavsc.Model.Calendar
/// <summary>
/// The image locator.
/// </summary>
[Display(ResourceType=typeof(LocalizedText),Name="ImgLocator")]
public string ImgLocator { get; set; }
[Display(ResourceType=typeof(LocalizedText),Name="Photo")]
public string Photo { get; set; }
}
}

@ -1,3 +1,22 @@
2015-10-28 Paul Schneider <paul@pschneider.fr>
* ICalendarManager.cs: WIP booking TODO a calendar provider
* YaEvent.cs:
* IFreeDateSet.cs: WIP booking
* LocalizedText.resx:
* LocalizedText.fr.resx:
* LocalizedText.Designer.cs:
* LocalizedText.fr.Designer.cs: implements the message "uses
cookies"
* YavscModel.csproj: refactoring
* Notification.cs: The Yavsc otification will start as a
Google one ...
many properties are not yet used, but all seems usefull.
2015-10-27 Paul Schneider <paul@pschneider.fr>
* YavscModel.csproj:

@ -388,6 +388,12 @@ namespace Yavsc.Model {
}
}
public static string ThisSiteUsesCookies {
get {
return ResourceManager.GetString("ThisSiteUsesCookies", resourceCulture);
}
}
public static string ReadMore {
get {
return ResourceManager.GetString("ReadMore", resourceCulture);

@ -64,6 +64,12 @@ namespace Yavsc.Model {
}
}
public static string Location {
get {
return ResourceManager.GetString("Location", resourceCulture);
}
}
public static string Circles {
get {
return ResourceManager.GetString("Circles", resourceCulture);
@ -208,12 +214,6 @@ namespace Yavsc.Model {
}
}
public static string Tag_name {
get {
return ResourceManager.GetString("Tag_name", resourceCulture);
}
}
public static string Google_error {
get {
return ResourceManager.GetString("Google_error", resourceCulture);
@ -274,9 +274,9 @@ namespace Yavsc.Model {
}
}
public static string Location {
public static string Tag_name {
get {
return ResourceManager.GetString("Location", resourceCulture);
return ResourceManager.GetString("Tag_name", resourceCulture);
}
}
@ -376,6 +376,12 @@ namespace Yavsc.Model {
}
}
public static string ThisSiteUsesCookies {
get {
return ResourceManager.GetString("ThisSiteUsesCookies", resourceCulture);
}
}
public static string ReadMore {
get {
return ResourceManager.GetString("ReadMore", resourceCulture);

@ -75,9 +75,10 @@
<data name="role_created"><value>Rôle créé</value></data>
<data name="StartDate"><value>Date de démarrage</value></data>
<data name="Submit"><value>Soumettre</value></data>
<data name="Tag_name"><value>Nom du tag</value></data>
<data name="Tex_version"><value>Version LaTeX</value></data>
<data name="ThisSiteUsesCookies"><value>Ce site utilise les cookies</value></data>
<data name="Title"><value>Titre</value></data>
<data name="Tag_name"><value>Nom du tag</value></data>
<data name="Unitary_cost"><value>Coût unitaire</value></data>
<data name="User_List"><value>Liste des utilisateurs</value><comment></comment></data>
<data name="User_name"><value>Nom d'utilisateur</value></data>

@ -77,9 +77,10 @@
<data name="role_created"><value>role created</value></data>
<data name="StartDate"><value>Start date</value></data>
<data name="Submit"><value>Submit</value></data>
<data name="Tag_name"><value>Tag name</value></data>
<data name="Tex_version"><value>LaTeX version</value></data>
<data name="ThisSiteUsesCookies"><value>This site uses cookies</value></data>
<data name="Title"><value>Title</value></data>
<data name="Tag_name"><value>Tag name</value></data>
<data name="Unitary_cost"><value>Unitary_cost</value></data>
<data name="User_List"><value>User List</value><comment></comment></data>
<data name="User_name"><value>User name</value></data>

@ -1,5 +1,5 @@
//
// Message.cs
// Notification.cs
//
// Author:
// Paul Schneider <paul@pschneider.fr>
@ -22,31 +22,11 @@ using System;
namespace Yavsc.Model.Messaging
{
/// <summary>
/// Simple message.
/// </summary>
public class SimpleMessage
public class Notification : Google.Notification
{
/// <summary>
/// Gets or sets the user name this message is comming from.
/// </summary>
/// <value>From.</value>
public string From { get; set; }
/// <summary>
/// Gets or sets the user names, separted by semilicon to which this message will be sent.
/// </summary>
/// <value>To.</value>
public string To { get; set; }
/// <summary>
/// Gets or sets the subject.
/// </summary>
/// <value>The subject.</value>
public string Subject { get; set; }
/// <summary>
/// Gets or sets the body.
/// </summary>
/// <value>The body.</value>
public string Body { get; set; }
public Notification ()
{
}
}
}

@ -155,8 +155,6 @@
<Compile Include="RolesAndMembers\ProviderPublicInfo.cs" />
<Compile Include="RolesAndMembers\GCMRegister.cs" />
<Compile Include="RolesAndMembers\LostPasswordModel.cs" />
<Compile Include="Messaging\SimpleMessage.cs" />
<Compile Include="Calendar\FreeDate.cs" />
<Compile Include="FrontOffice\Catalog\Billing\PriceOnItemCount.cs" />
<Compile Include="WorkFlow\TaskOutput.cs" />
<Compile Include="WorkFlow\FinalStateException.cs" />
@ -178,6 +176,9 @@
<Compile Include="Blogs\TagInfo.cs" />
<Compile Include="Blogs\MarkdownHelper.cs" />
<Compile Include="Calendar\BookQuery.cs" />
<Compile Include="Calendar\IFreeDateSet.cs" />
<Compile Include="Calendar\ICalendarManager.cs" />
<Compile Include="Messaging\Notification.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

Loading…