Merge pull request #65 from Taloth/feed-ical
New: Added iCal feed for the calendar
This commit is contained in:
commit
d24eaaa784
|
@ -0,0 +1,66 @@
|
||||||
|
using Nancy;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using DDay.iCal;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using Nancy.Responses;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Calendar
|
||||||
|
{
|
||||||
|
public class CalendarFeedModule : NzbDroneFeedModule
|
||||||
|
{
|
||||||
|
private readonly IEpisodeService _episodeService;
|
||||||
|
|
||||||
|
public CalendarFeedModule(IEpisodeService episodeService)
|
||||||
|
: base("calendar")
|
||||||
|
{
|
||||||
|
_episodeService = episodeService;
|
||||||
|
|
||||||
|
Get["/NzbDrone.ics"] = options => GetCalendarFeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response GetCalendarFeed()
|
||||||
|
{
|
||||||
|
var start = DateTime.Today.AddDays(-7);
|
||||||
|
var end = DateTime.Today.AddDays(28);
|
||||||
|
|
||||||
|
var queryStart = Request.Query.Start;
|
||||||
|
var queryEnd = Request.Query.End;
|
||||||
|
|
||||||
|
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
|
||||||
|
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
|
||||||
|
|
||||||
|
var episodes = _episodeService.EpisodesBetweenDates(start, end);
|
||||||
|
var icalCalendar = new iCalendar();
|
||||||
|
|
||||||
|
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
|
||||||
|
{
|
||||||
|
var occurrence = icalCalendar.Create<Event>();
|
||||||
|
occurrence.UID = "NzbDrone_episode_" + episode.Id.ToString();
|
||||||
|
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
|
||||||
|
occurrence.Start = new iCalDateTime(episode.AirDateUtc.Value);
|
||||||
|
occurrence.End = new iCalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime));
|
||||||
|
occurrence.Description = episode.Overview;
|
||||||
|
occurrence.Categories = new List<string>() { episode.Series.Network };
|
||||||
|
|
||||||
|
switch (episode.Series.SeriesType)
|
||||||
|
{
|
||||||
|
case SeriesTypes.Daily:
|
||||||
|
occurrence.Summary = string.Format("{0} - {1}", episode.Series.Title, episode.Title);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
occurrence.Summary = string.Format("{0} - {1}x{2:00} - {3}", episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var serializer = new DDay.iCal.Serialization.iCalendar.SerializerFactory().Build(icalCalendar.GetType(), new DDay.iCal.Serialization.SerializationContext()) as DDay.iCal.Serialization.IStringSerializer;
|
||||||
|
var icalendar = serializer.SerializeToString(icalCalendar);
|
||||||
|
|
||||||
|
return new TextResponse(icalendar, "text/calendar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,9 @@
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="DDay.iCal">
|
||||||
|
<HintPath>..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Microsoft.AspNet.SignalR.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
<Reference Include="Microsoft.AspNet.SignalR.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.1.1.3\lib\net40\Microsoft.AspNet.SignalR.Core.dll</HintPath>
|
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.1.1.3\lib\net40\Microsoft.AspNet.SignalR.Core.dll</HintPath>
|
||||||
|
@ -88,6 +91,7 @@
|
||||||
<Compile Include="Blacklist\BlacklistModule.cs" />
|
<Compile Include="Blacklist\BlacklistModule.cs" />
|
||||||
<Compile Include="Blacklist\BlacklistResource.cs" />
|
<Compile Include="Blacklist\BlacklistResource.cs" />
|
||||||
<Compile Include="Calendar\CalendarModule.cs" />
|
<Compile Include="Calendar\CalendarModule.cs" />
|
||||||
|
<Compile Include="Calendar\CalendarFeedModule.cs" />
|
||||||
<Compile Include="ClientSchema\SchemaDeserializer.cs" />
|
<Compile Include="ClientSchema\SchemaDeserializer.cs" />
|
||||||
<Compile Include="ClientSchema\FieldDefinitionAttribute.cs" />
|
<Compile Include="ClientSchema\FieldDefinitionAttribute.cs" />
|
||||||
<Compile Include="ClientSchema\Field.cs" />
|
<Compile Include="ClientSchema\Field.cs" />
|
||||||
|
@ -139,6 +143,7 @@
|
||||||
<Compile Include="Metadata\MetadataResource.cs" />
|
<Compile Include="Metadata\MetadataResource.cs" />
|
||||||
<Compile Include="Metadata\MetadataModule.cs" />
|
<Compile Include="Metadata\MetadataModule.cs" />
|
||||||
<Compile Include="Notifications\NotificationSchemaModule.cs" />
|
<Compile Include="Notifications\NotificationSchemaModule.cs" />
|
||||||
|
<Compile Include="NzbDroneFeedModule.cs" />
|
||||||
<Compile Include="ProviderResource.cs" />
|
<Compile Include="ProviderResource.cs" />
|
||||||
<Compile Include="ProviderModuleBase.cs" />
|
<Compile Include="ProviderModuleBase.cs" />
|
||||||
<Compile Include="Indexers\IndexerSchemaModule.cs" />
|
<Compile Include="Indexers\IndexerSchemaModule.cs" />
|
||||||
|
@ -199,7 +204,9 @@
|
||||||
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
<Compile Include="Validation\RuleBuilderExtensions.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
|
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj">
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
using Nancy;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api
|
||||||
|
{
|
||||||
|
public abstract class NzbDroneFeedModule : NancyModule
|
||||||
|
{
|
||||||
|
protected NzbDroneFeedModule(string resource)
|
||||||
|
: base("/feed/" + resource.Trim('/'))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
|
<package id="DDay.iCal" version="1.0.2.575" targetFramework="net40" />
|
||||||
<package id="FluentValidation" version="5.0.0.1" targetFramework="net40" />
|
<package id="FluentValidation" version="5.0.0.1" targetFramework="net40" />
|
||||||
<package id="Nancy" version="0.21.1" targetFramework="net40" />
|
<package id="Nancy" version="0.21.1" targetFramework="net40" />
|
||||||
<package id="Nancy.Authentication.Basic" version="0.21.1" targetFramework="net40" />
|
<package id="Nancy.Authentication.Basic" version="0.21.1" targetFramework="net40" />
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'marionette',
|
||||||
|
'System/StatusModel',
|
||||||
|
'Mixins/CopyToClipboard'
|
||||||
|
], function (Marionette, StatusModel) {
|
||||||
|
return Marionette.Layout.extend({
|
||||||
|
template: 'Calendar/CalendarFeedViewTemplate',
|
||||||
|
|
||||||
|
ui: {
|
||||||
|
icalUrl : '.x-ical-url',
|
||||||
|
icalCopy : '.x-ical-copy'
|
||||||
|
},
|
||||||
|
|
||||||
|
templateHelpers: {
|
||||||
|
icalHttpUrl : window.location.protocol + '//' + window.location.host + StatusModel.get('urlBase') + '/feed/calendar/NzbDrone.ics',
|
||||||
|
icalWebCalUrl : 'webcal://' + window.location.host + StatusModel.get('urlBase') + '/feed/calendar/NzbDrone.ics'
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow: function () {
|
||||||
|
this.ui.icalCopy.copyToClipboard(this.ui.icalUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h3>NzbDrone Calendar feed</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body edit-series-modal">
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">iCal feed</label>
|
||||||
|
|
||||||
|
<div class="controls ical-url">
|
||||||
|
<div class="input-append">
|
||||||
|
<input type="text" class="x-ical-url" value="{{icalHttpUrl}}" readonly="readonly" />
|
||||||
|
<button class="btn btn-icon-only x-ical-copy" title="Copy to clipboard"><i class="icon-copy"></i></button>
|
||||||
|
<a class="btn btn-icon-only no-router" title="Subscribe" href="{{icalWebCalUrl}}" target="_blank"><i class="icon-calendar-empty"></i></a>
|
||||||
|
</div>
|
||||||
|
<span class="help-inline">
|
||||||
|
<i class="icon-nd-form-info" title="Copy this url into your clients subscription form or use the subscribe button if your browser support webcal"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn" data-dismiss="modal">close</button>
|
||||||
|
</div>
|
|
@ -1,10 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
|
'AppLayout',
|
||||||
'marionette',
|
'marionette',
|
||||||
'Calendar/UpcomingCollectionView',
|
'Calendar/UpcomingCollectionView',
|
||||||
'Calendar/CalendarView'
|
'Calendar/CalendarView',
|
||||||
], function (Marionette, UpcomingCollectionView, CalendarView) {
|
'Calendar/CalendarFeedView'
|
||||||
|
], function (AppLayout, Marionette, UpcomingCollectionView, CalendarView, CalendarFeedView) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Calendar/CalendarLayoutTemplate',
|
template: 'Calendar/CalendarLayoutTemplate',
|
||||||
|
|
||||||
|
@ -12,6 +14,10 @@ define(
|
||||||
upcoming: '#x-upcoming',
|
upcoming: '#x-upcoming',
|
||||||
calendar: '#x-calendar'
|
calendar: '#x-calendar'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
events: {
|
||||||
|
'click .x-ical': '_showiCal'
|
||||||
|
},
|
||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
this._showUpcoming();
|
this._showUpcoming();
|
||||||
|
@ -24,6 +30,11 @@ define(
|
||||||
|
|
||||||
_showCalendar: function () {
|
_showCalendar: function () {
|
||||||
this.calendar.show(new CalendarView());
|
this.calendar.show(new CalendarView());
|
||||||
|
},
|
||||||
|
|
||||||
|
_showiCal: function () {
|
||||||
|
var view = new CalendarFeedView();
|
||||||
|
AppLayout.modalRegion.show(view);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="span3">
|
<div class="span3">
|
||||||
<h4>Upcoming</h4>
|
<div class="pull-left">
|
||||||
|
<h4>Upcoming</h4>
|
||||||
|
</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
<h4>
|
||||||
|
<i class="icon-calendar-empty ical x-ical"></i>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
<div id="x-upcoming"/>
|
<div id="x-upcoming"/>
|
||||||
</div>
|
</div>
|
||||||
<div class=span9>
|
<div class=span9>
|
||||||
|
|
|
@ -8,25 +8,24 @@ define(
|
||||||
'Calendar/Collection',
|
'Calendar/Collection',
|
||||||
'System/StatusModel',
|
'System/StatusModel',
|
||||||
'History/Queue/QueueCollection',
|
'History/Queue/QueueCollection',
|
||||||
|
'Config',
|
||||||
'Mixins/backbone.signalr.mixin',
|
'Mixins/backbone.signalr.mixin',
|
||||||
'fullcalendar',
|
'fullcalendar',
|
||||||
'jquery.easypiechart'
|
'jquery.easypiechart'
|
||||||
], function (vent, Marionette, moment, CalendarCollection, StatusModel, QueueCollection) {
|
], function (vent, Marionette, moment, CalendarCollection, StatusModel, QueueCollection, Config) {
|
||||||
|
|
||||||
var _instance;
|
|
||||||
|
|
||||||
return Marionette.ItemView.extend({
|
return Marionette.ItemView.extend({
|
||||||
|
storageKey: 'calendar.view',
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
this.collection = new CalendarCollection().bindSignalR({ updateOnly: true });
|
this.collection = new CalendarCollection().bindSignalR({ updateOnly: true });
|
||||||
this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
|
this.listenTo(this.collection, 'change', this._reloadCalendarEvents);
|
||||||
this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents);
|
this.listenTo(QueueCollection, 'sync', this._reloadCalendarEvents);
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function () {
|
render : function () {
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.$el.empty().fullCalendar({
|
this.$el.empty().fullCalendar({
|
||||||
defaultView : 'basicWeek',
|
defaultView : Config.getValue(this.storageKey, 'basicWeek'),
|
||||||
allDayDefault : false,
|
allDayDefault : false,
|
||||||
ignoreTimezone: false,
|
ignoreTimezone: false,
|
||||||
weekMode : 'variable',
|
weekMode : 'variable',
|
||||||
|
@ -41,54 +40,62 @@ define(
|
||||||
prev: '<i class="icon-arrow-left"></i>',
|
prev: '<i class="icon-arrow-left"></i>',
|
||||||
next: '<i class="icon-arrow-right"></i>'
|
next: '<i class="icon-arrow-right"></i>'
|
||||||
},
|
},
|
||||||
viewRender : this._getEvents,
|
viewRender : this._viewRender.bind(this),
|
||||||
eventRender : function (event, element) {
|
eventRender : this._eventRender.bind(this),
|
||||||
self.$(element).addClass(event.statusLevel);
|
|
||||||
self.$(element).children('.fc-event-inner').addClass(event.statusLevel);
|
|
||||||
|
|
||||||
if (event.progress > 0) {
|
|
||||||
self.$(element).find('.fc-event-time')
|
|
||||||
.after('<span class="chart pull-right" data-percent="{0}"></span>'.format(event.progress));
|
|
||||||
|
|
||||||
self.$(element).find('.chart').easyPieChart({
|
|
||||||
barColor : '#ffffff',
|
|
||||||
trackColor: false,
|
|
||||||
scaleColor: false,
|
|
||||||
lineWidth : 2,
|
|
||||||
size : 14,
|
|
||||||
animate : false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
eventClick : function (event) {
|
eventClick : function (event) {
|
||||||
vent.trigger(vent.Commands.ShowEpisodeDetails, {episode: event.model});
|
vent.trigger(vent.Commands.ShowEpisodeDetails, {episode: event.model});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_instance = this;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow: function () {
|
onShow: function () {
|
||||||
this.$('.fc-button-today').click();
|
this.$('.fc-button-today').click();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_viewRender: function (view) {
|
||||||
|
if (Config.getValue(this.storageKey) !== view.name) {
|
||||||
|
Config.setValue(this.storageKey, view.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._getEvents(view);
|
||||||
|
},
|
||||||
|
|
||||||
|
_eventRender: function (event, element) {
|
||||||
|
this.$(element).addClass(event.statusLevel);
|
||||||
|
this.$(element).children('.fc-event-inner').addClass(event.statusLevel);
|
||||||
|
|
||||||
|
if (event.progress > 0) {
|
||||||
|
this.$(element).find('.fc-event-time')
|
||||||
|
.after('<span class="chart pull-right" data-percent="{0}"></span>'.format(event.progress));
|
||||||
|
|
||||||
|
this.$(element).find('.chart').easyPieChart({
|
||||||
|
barColor : '#ffffff',
|
||||||
|
trackColor: false,
|
||||||
|
scaleColor: false,
|
||||||
|
lineWidth : 2,
|
||||||
|
size : 14,
|
||||||
|
animate : false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_getEvents: function (view) {
|
_getEvents: function (view) {
|
||||||
var start = moment(view.visStart).toISOString();
|
var start = moment(view.visStart).toISOString();
|
||||||
var end = moment(view.visEnd).toISOString();
|
var end = moment(view.visEnd).toISOString();
|
||||||
|
|
||||||
_instance.$el.fullCalendar('removeEvents');
|
this.$el.fullCalendar('removeEvents');
|
||||||
|
|
||||||
_instance.collection.fetch({
|
this.collection.fetch({
|
||||||
data : { start: start, end: end },
|
data : { start: start, end: end },
|
||||||
success: function (collection) {
|
success: this._setEventData.bind(this)
|
||||||
_instance._setEventData(collection);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_setEventData: function (collection) {
|
_setEventData: function (collection) {
|
||||||
var events = [];
|
var events = [];
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
collection.each(function (model) {
|
collection.each(function (model) {
|
||||||
var seriesTitle = model.get('series').title;
|
var seriesTitle = model.get('series').title;
|
||||||
var start = model.get('airDateUtc');
|
var start = model.get('airDateUtc');
|
||||||
|
@ -100,15 +107,15 @@ define(
|
||||||
start : start,
|
start : start,
|
||||||
end : end,
|
end : end,
|
||||||
allDay : false,
|
allDay : false,
|
||||||
statusLevel : _instance._getStatusLevel(model, end),
|
statusLevel : self._getStatusLevel(model, end),
|
||||||
progress : _instance._getDownloadProgress(model),
|
progress : self._getDownloadProgress(model),
|
||||||
model : model
|
model : model
|
||||||
};
|
};
|
||||||
|
|
||||||
events.push(event);
|
events.push(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
_instance.$el.fullCalendar('addEventSource', events);
|
this.$el.fullCalendar('addEventSource', events);
|
||||||
},
|
},
|
||||||
|
|
||||||
_getStatusLevel: function (element, endTime) {
|
_getStatusLevel: function (element, endTime) {
|
||||||
|
|
|
@ -158,3 +158,17 @@
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ical
|
||||||
|
{
|
||||||
|
color: @btnInverseBackground;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ical-url {
|
||||||
|
|
||||||
|
input {
|
||||||
|
width : 440px;
|
||||||
|
cursor : text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
<link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png?v=2"/>
|
<link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png?v=2"/>
|
||||||
<link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png?v=2"/>
|
<link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png?v=2"/>
|
||||||
<link rel="icon" type="image/ico" href="/Content/Images/favicon.ico?v=2"/>
|
<link rel="icon" type="image/ico" href="/Content/Images/favicon.ico?v=2"/>
|
||||||
|
|
||||||
|
<link rel="alternate" type="text/calendar" title="iCalendar feed for NzbDrone" href="/feed/calendar/NzbDrone.ics" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="nav-region"></div>
|
<div id="nav-region"></div>
|
||||||
|
|
|
@ -29,17 +29,21 @@ define(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
var href = event.target.getAttribute('href');
|
var href = event.target.getAttribute('href');
|
||||||
|
|
||||||
if (!href && $target.closest('a') && $target.closest('a')[0]) {
|
if (!href && $target.closest('a') && $target.closest('a')[0]) {
|
||||||
|
|
||||||
var linkElement = $target.closest('a')[0];
|
var linkElement = $target.closest('a')[0];
|
||||||
|
|
||||||
|
if ($(linkElement).hasClass('no-router')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
href = linkElement.getAttribute('href');
|
href = linkElement.getAttribute('href');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (!href) {
|
if (!href) {
|
||||||
throw 'couldn\'t find route target';
|
throw 'couldn\'t find route target';
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue