Browse Source

Started with twitter integration.

pull/314/head
Sebastian Stehle 7 years ago
parent
commit
11d1eedf88
  1. 24
      src/Squidex.Domain.Apps.Core.Model/Rules/Actions/TweetAction.cs
  2. 21
      src/Squidex.Domain.Apps.Core.Model/Rules/Actions/TwitterOptions.cs
  3. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs
  4. 74
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/TweetActionHandler.cs
  5. 15
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/ClientPool.cs
  6. 1
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  7. 12
      src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs
  8. 7
      src/Squidex/AppServices.cs
  9. 4
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  10. 2
      src/Squidex/Areas/Api/Controllers/Contents/MyContentsControllerOptions.cs
  11. 35
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/TweetActionDto.cs
  12. 5
      src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs
  13. 33
      src/Squidex/Areas/Api/Controllers/Rules/TwitterController.cs
  14. 5
      src/Squidex/Areas/Api/Controllers/UI/Models/UISettingsDto.cs
  15. 9
      src/Squidex/Areas/Api/Controllers/UI/UIController.cs
  16. 3
      src/Squidex/Config/Domain/RuleServices.cs
  17. 3
      src/Squidex/app/features/rules/declarations.ts
  18. 2
      src/Squidex/app/features/rules/module.ts
  19. 31
      src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.html
  20. 2
      src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.scss
  21. 37
      src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts
  22. 7
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  23. 3
      src/Squidex/app/shared/services/rules.service.ts
  24. 5
      src/Squidex/app/theme/_rules.scss
  25. 8
      src/Squidex/app/theme/icomoon/demo-files/demo.css
  26. 1230
      src/Squidex/app/theme/icomoon/demo.html
  27. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  28. 1
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  29. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  30. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  31. 2174
      src/Squidex/app/theme/icomoon/selection.json
  32. 157
      src/Squidex/app/theme/icomoon/style.css
  33. 15
      src/Squidex/appsettings.json
  34. 43
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/TweetActionTests.cs

24
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/TweetAction.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Actions
{
[TypeName(nameof(TweetAction))]
public sealed class TweetAction : RuleAction
{
public string PinCode { get; set; }
public string Text { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

21
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/TwitterOptions.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Rules.Actions
{
public sealed class TwitterOptions
{
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public bool IsConfigured()
{
return !string.IsNullOrWhiteSpace(ClientId) && !string.IsNullOrWhiteSpace(ClientSecret);
}
}
}

2
src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs

@ -23,6 +23,8 @@ namespace Squidex.Domain.Apps.Core.Rules
T Visit(SlackAction action);
T Visit(TweetAction action);
T Visit(WebhookAction action);
}
}

74
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/TweetActionHandler.cs

@ -0,0 +1,74 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using CoreTweet;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{
public sealed class TweetJob
{
public string PinCode { get; set; }
public string Text { get; set; }
}
public sealed class TweetActionHandler : RuleActionHandler<TweetAction, TweetJob>
{
private const string Description = "Send a tweet";
private readonly RuleEventFormatter formatter;
private readonly ClientPool<string, Tokens> tokenPool;
public TweetActionHandler(RuleEventFormatter formatter, IOptions<TwitterOptions> twitterOptions)
{
Guard.NotNull(formatter, nameof(formatter));
Guard.NotNull(twitterOptions, nameof(twitterOptions));
this.formatter = formatter;
tokenPool = new ClientPool<string, Tokens>(async key =>
{
var session = await OAuth.AuthorizeAsync(twitterOptions.Value.ClientId, twitterOptions.Value.ClientSecret);
return await session.GetTokensAsync(key);
});
}
protected override (string Description, TweetJob Data) CreateJob(EnrichedEvent @event, TweetAction action)
{
var text = formatter.Format(action.Text, @event);
var ruleJob = new TweetJob { Text = text, PinCode = action.PinCode };
return (Description, ruleJob);
}
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(TweetJob job)
{
try
{
var tokens = await tokenPool.GetClientAsync(job.PinCode);
var response = await tokens.Statuses.UpdateAsync(x => job.Text);
return ($"Tweeted: {job.Text}", null);
}
catch (Exception ex)
{
return (ex.Message, ex);
}
}
}
}

15
src/Squidex.Domain.Apps.Core.Operations/HandleRules/ClientPool.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
@ -17,18 +18,28 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
private static readonly TimeSpan TTL = TimeSpan.FromMinutes(30);
private readonly MemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
private readonly Func<TKey, TClient> factory;
private readonly Func<TKey, Task<TClient>> factory;
public ClientPool(Func<TKey, TClient> factory)
{
this.factory = x => Task.FromResult(factory(x));
}
public ClientPool(Func<TKey, Task<TClient>> factory)
{
this.factory = factory;
}
public TClient GetClient(TKey key)
{
return GetClientAsync(key).Result;
}
public async Task<TClient> GetClientAsync(TKey key)
{
if (!memoryCache.TryGetValue<TClient>(key, out var client))
{
client = factory(key);
client = await factory(key);
memoryCache.Set(key, client, TTL);
}

1
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -15,6 +15,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="5.1.0" />
<PackageReference Include="CoreTweet" Version="0.9.0.415" />
<PackageReference Include="Elasticsearch.Net" Version="6.2.0" />
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.0" />

12
src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs

@ -129,6 +129,18 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(TweetAction action)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(action.PinCode))
{
errors.Add(new ValidationError("Pin Code is required.", nameof(action.PinCode)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(SlackAction action)
{
var errors = new List<ValidationError>();

7
src/Squidex/AppServices.cs

@ -14,6 +14,7 @@ using Squidex.Config;
using Squidex.Config.Authentication;
using Squidex.Config.Domain;
using Squidex.Config.Web;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Commands;
namespace Squidex
@ -44,7 +45,11 @@ namespace Squidex
services.Configure<ReadonlyOptions>(
config.GetSection("mode"));
services.Configure<ContentsControllerOptions>(
services.Configure<TwitterOptions>(
config.GetSection("twitter"));
services.Configure<MyContentsControllerOptions>(
config.GetSection("contentsController"));
services.Configure<MyUrlsOptions>(
config.GetSection("urls"));

4
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -30,14 +30,14 @@ namespace Squidex.Areas.Api.Controllers.Contents
[SwaggerIgnore]
public sealed class ContentsController : ApiController
{
private readonly IOptions<ContentsControllerOptions> controllerOptions;
private readonly IOptions<MyContentsControllerOptions> controllerOptions;
private readonly IContentQueryService contentQuery;
private readonly IGraphQLService graphQl;
public ContentsController(ICommandBus commandBus,
IContentQueryService contentQuery,
IGraphQLService graphQl,
IOptions<ContentsControllerOptions> controllerOptions)
IOptions<MyContentsControllerOptions> controllerOptions)
: base(commandBus)
{
this.contentQuery = contentQuery;

2
src/Squidex/Areas/Api/Controllers/Contents/ContentsControllerOptions.cs → src/Squidex/Areas/Api/Controllers/Contents/MyContentsControllerOptions.cs

@ -7,7 +7,7 @@
namespace Squidex.Areas.Api.Controllers.Contents
{
public sealed class ContentsControllerOptions
public sealed class MyContentsControllerOptions
{
public bool EnableSurrogateKeys { get; set; }

35
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/TweetActionDto.cs

@ -0,0 +1,35 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("Tweet")]
public sealed class TweetActionDto : RuleActionDto
{
/// <summary>
/// The pin code.
/// </summary>
[Required]
public string PinCode { get; set; }
/// <summary>
/// The text that is sent as tweet to twitter.
/// </summary>
public string Text { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new TweetAction());
}
}
}

5
src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs

@ -55,6 +55,11 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters
return SimpleMapper.Map(action, new SlackActionDto());
}
public RuleActionDto Visit(TweetAction action)
{
return SimpleMapper.Map(action, new TweetActionDto());
}
public RuleActionDto Visit(WebhookAction action)
{
return SimpleMapper.Map(action, new WebhookActionDto());

33
src/Squidex/Areas/Api/Controllers/Rules/TwitterController.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using CoreTweet;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Rules.Actions;
namespace Squidex.Areas.Api.Controllers.Rules
{
public sealed class TwitterController : Controller
{
private readonly TwitterOptions twitterOptions;
public TwitterController(IOptions<TwitterOptions> twitterOptions)
{
this.twitterOptions = twitterOptions.Value;
}
[Route("rules/twitter/auth")]
public async Task<IActionResult> Auth()
{
var session = await OAuth.AuthorizeAsync(twitterOptions.ClientId, twitterOptions.ClientSecret);
return Redirect(session.AuthorizeUri.ToString());
}
}
}

5
src/Squidex/Areas/Api/Controllers/UI/Models/UISettingsDto.cs

@ -22,5 +22,10 @@ namespace Squidex.Areas.Api.Controllers.UI.Models
/// </summary>
[Required]
public string MapKey { get; set; }
/// <summary>
/// Indicates whether twitter actions are supported.
/// </summary>
public bool SupportsTwitterActions { get; set; }
}
}

9
src/Squidex/Areas/Api/Controllers/UI/UIController.cs

@ -10,6 +10,7 @@ using Microsoft.Extensions.Options;
using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.UI.Models;
using Squidex.Config;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -23,10 +24,13 @@ namespace Squidex.Areas.Api.Controllers.UI
public sealed class UIController : ApiController
{
private readonly MyUIOptions uiOptions;
private readonly TwitterOptions twitterOptions;
public UIController(ICommandBus commandBus, IOptions<MyUIOptions> uiOptions)
public UIController(ICommandBus commandBus, IOptions<MyUIOptions> uiOptions, IOptions<TwitterOptions> twitterOptions)
: base(commandBus)
{
this.twitterOptions = twitterOptions.Value;
this.uiOptions = uiOptions.Value;
}
@ -42,7 +46,8 @@ namespace Squidex.Areas.Api.Controllers.UI
var dto = new UISettingsDto
{
MapType = uiOptions.Map?.Type ?? "OSM",
MapKey = uiOptions.Map?.GoogleMaps?.Key
MapKey = uiOptions.Map?.GoogleMaps?.Key,
SupportsTwitterActions = twitterOptions.IsConfigured()
};
return Ok(dto);

3
src/Squidex/Config/Domain/RuleServices.cs

@ -42,6 +42,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<MediumActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<TweetActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<SlackActionHandler>()
.As<IRuleActionHandler>();

3
src/Squidex/app/features/rules/declarations.ts

@ -11,9 +11,12 @@ export * from './pages/rules/actions/elastic-search-action.component';
export * from './pages/rules/actions/fastly-action.component';
export * from './pages/rules/actions/medium-action.component';
export * from './pages/rules/actions/slack-action.component';
export * from './pages/rules/actions/tweet-action.component';
export * from './pages/rules/actions/webhook-action.component';
export * from './pages/rules/triggers/asset-changed-trigger.component';
export * from './pages/rules/triggers/content-changed-trigger.component';
export * from './pages/rules/rule-wizard.component';
export * from './pages/rules/rules-page.component';

2
src/Squidex/app/features/rules/module.ts

@ -27,6 +27,7 @@ import {
RulesPageComponent,
RuleWizardComponent,
SlackActionComponent,
TweetActionComponent,
WebhookActionComponent
} from './declarations';
@ -69,6 +70,7 @@ const routes: Routes = [
RulesPageComponent,
RuleWizardComponent,
SlackActionComponent,
TweetActionComponent,
WebhookActionComponent
]
})

31
src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.html

@ -0,0 +1,31 @@
<h3 class="wizard-title">Tweet a status update to your twitter feed</h3>
<form [formGroup]="actionForm" class="form-horizontal">
<div class="form-group row">
<label class="col col-3 col-form-label" for="pinCode">Pin Code</label>
<div class="col col-9">
<sqx-control-errors for="pinCode" [submitted]="actionFormSubmitted"></sqx-control-errors>
<input type="pinCode" class="form-control" id="pinCode" formControlName="pinCode" />
<small class="form-text text-muted">
The pin code to authorize. Follow this link and add the pin code: <a href="/api/rules/twitter/auth" target="_blank">Login to twitter</a>.
</small>
</div>
</div>
<div class="form-group row">
<label class="col col-3 col-form-label" for="text">Text</label>
<div class="col col-9">
<sqx-control-errors for="text" [submitted]="actionFormSubmitted"></sqx-control-errors>
<textarea class="form-control" id="text" formControlName="text"></textarea>
<small class="form-text text-muted">
The text to tweet. Read the <a routerLink="help">help</a> section for information about advanced formatting.
</small>
</div>
</div>
</form>

2
src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.scss

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

37
src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts

@ -0,0 +1,37 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'sqx-tweet-action',
styleUrls: ['./tweet-action.component.scss'],
templateUrl: './tweet-action.component.html'
})
export class TweetActionComponent implements OnInit {
@Input()
public action: any;
@Input()
public actionForm: FormGroup;
@Input()
public actionFormSubmitted = false;
public ngOnInit() {
this.actionForm.setControl('pinCode',
new FormControl(this.action.pinCode || '', [
Validators.required
]));
this.actionForm.setControl('text',
new FormControl(this.action.text || '', [
Validators.required
]));
}
}

7
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html

@ -114,6 +114,13 @@
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-slack-action>
</ng-container>
<ng-container *ngSwitchCase="'Tweet'">
<sqx-tweet-action
[action]="action"
[actionForm]="actionForm.form"
[actionFormSubmitted]="actionForm.submitted | async">
</sqx-tweet-action>
</ng-container>
<ng-container *ngSwitchCase="'Webhook'">
<sqx-webhook-action
[action]="action"

3
src/Squidex/app/shared/services/rules.service.ts

@ -49,6 +49,9 @@ export const ruleActions: any = {
'Slack': {
name: 'Send to Slack'
},
'Tweet': {
name: 'Tweet'
},
'Webhook': {
name: 'Send Webhook'
}

5
src/Squidex/app/theme/_rules.scss

@ -11,6 +11,7 @@ $action-slack: #5c3a58;
$action-azure: #55b3ff;
$action-fastly: #e23335;
$action-medium: #00ab6c;
$action-twitter: #1da1f2;
// sass-lint:disable class-name-format
@ -88,6 +89,10 @@ $action-medium: #00ab6c;
@include build-element($action-slack);
}
.rule-element-Tweet {
@include build-element($action-twitter);
}
.rule-element-Webhook {
@include build-element($action-webhook);
}

8
src/Squidex/app/theme/icomoon/demo-files/demo.css

@ -147,16 +147,16 @@ p {
font-size: 16px;
}
.fs1 {
font-size: 24px;
font-size: 32px;
}
.fs2 {
font-size: 32px;
font-size: 24px;
}
.fs3 {
font-size: 28px;
font-size: 32px;
}
.fs4 {
font-size: 32px;
font-size: 28px;
}
.fs5 {
font-size: 32px;

1230
src/Squidex/app/theme/icomoon/demo.html

File diff suppressed because it is too large

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

1
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -99,6 +99,7 @@
<glyph unicode="&#xe959;" glyph-name="action-Medium" d="M121.429 688.64c1.28 12.587-3.541 25.003-12.928 33.451l-95.573 115.2v17.195h296.832l229.461-503.253 201.728 503.253h283.051v-17.195l-81.792-78.379c-7.040-5.376-10.539-14.208-9.088-22.955v-576c-1.451-8.704 2.048-17.493 9.088-22.912l79.787-78.379v-17.195h-401.493v17.195l82.645 80.299c8.107 8.107 8.107 10.496 8.107 22.955v465.536l-229.973-584.021h-31.019l-267.733 584.021v-391.424c-2.219-16.469 3.243-33.024 14.805-44.928l107.52-130.56v-17.152h-304.853v17.195l107.52 130.56c11.52 11.861 16.64 28.587 13.909 44.885v452.608z" />
<glyph unicode="&#xe95a;" glyph-name="support" d="M726 426.667c0-24-20-42-44-42h-426l-170-172v598c0 24 18 42 42 42h554c24 0 44-18 44-42v-384zM896 682.667c24 0 42-18 42-42v-640l-170 170h-470c-24 0-42 18-42 42v86h554v384h86z" />
<glyph unicode="&#xe95b;" glyph-name="backup" d="M512 810.667c212 0 384-172 384-384s-172-384-384-384c-88 0-170 30-234 80l60 60c50-34 110-54 174-54 166 0 298 132 298 298s-132 298-298 298-298-132-298-298h128l-172-170-170 170h128c0 212 172 384 384 384zM598 426.667c0-46-40-86-86-86s-86 40-86 86 40 86 86 86 86-40 86-86z" />
<glyph unicode="&#xe95c;" glyph-name="twitter, action-Tweet" d="M1024 733.6c-37.6-16.8-78.2-28-120.6-33 43.4 26 76.6 67.2 92.4 116.2-40.6-24-85.6-41.6-133.4-51-38.4 40.8-93 66.2-153.4 66.2-116 0-210-94-210-210 0-16.4 1.8-32.4 5.4-47.8-174.6 8.8-329.4 92.4-433 219.6-18-31-28.4-67.2-28.4-105.6 0-72.8 37-137.2 93.4-174.8-34.4 1-66.8 10.6-95.2 26.2 0-0.8 0-1.8 0-2.6 0-101.8 72.4-186.8 168.6-206-17.6-4.8-36.2-7.4-55.4-7.4-13.6 0-26.6 1.4-39.6 3.8 26.8-83.4 104.4-144.2 196.2-146-72-56.4-162.4-90-261-90-17 0-33.6 1-50.2 3 93.2-59.8 203.6-94.4 322.2-94.4 386.4 0 597.8 320.2 597.8 597.8 0 9.2-0.2 18.2-0.6 27.2 41 29.4 76.6 66.4 104.8 108.6z" />
<glyph unicode="&#xe9ca;" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
<glyph unicode="&#xf00a;" glyph-name="grid" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf0c9;" glyph-name="list" horiz-adv-x="878" d="M877.714 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 768v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

2174
src/Squidex/app/theme/icomoon/selection.json

File diff suppressed because it is too large

157
src/Squidex/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?za0y3d');
src: url('fonts/icomoon.eot?za0y3d#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?za0y3d') format('truetype'),
url('fonts/icomoon.woff?za0y3d') format('woff'),
url('fonts/icomoon.svg?za0y3d#icomoon') format('svg');
src: url('fonts/icomoon.eot?f9httd');
src: url('fonts/icomoon.eot?f9httd#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?f9httd') format('truetype'),
url('fonts/icomoon.woff?f9httd') format('woff'),
url('fonts/icomoon.svg?f9httd#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,60 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-twitter:before {
content: "\e95c";
}
.icon-action-Tweet:before {
content: "\e95c";
}
.icon-hour-glass:before {
content: "\e954";
}
.icon-spinner:before {
content: "\e953";
}
.icon-clock:before {
content: "\e950";
}
.icon-bin2:before {
content: "\e902";
}
.icon-earth:before {
content: "\e9ca";
}
.icon-elapsed:before {
content: "\e943";
}
.icon-google:before {
content: "\e93b";
}
.icon-lock:before {
content: "\e934";
}
.icon-microsoft:before {
content: "\e940";
}
.icon-action-AzureQueue:before {
content: "\e940";
}
.icon-pause:before {
content: "\e92f";
}
.icon-play:before {
content: "\e930";
}
.icon-reset:before {
content: "\e92e";
}
.icon-settings2:before {
content: "\e92d";
}
.icon-timeout:before {
content: "\e944";
}
.icon-unlocked:before {
content: "\e933";
}
.icon-backup:before {
content: "\e95b";
}
@ -36,6 +90,51 @@
.icon-download:before {
content: "\e93e";
}
.icon-action-Algolia:before {
content: "\e94c";
}
.icon-type-Tags:before {
content: "\e94a";
}
.icon-activity:before {
content: "\e904";
}
.icon-history:before {
content: "\e904";
}
.icon-time:before {
content: "\e904";
}
.icon-add:before {
content: "\e905";
}
.icon-plus:before {
content: "\e905";
}
.icon-check-circle:before {
content: "\e906";
}
.icon-check-circle-filled:before {
content: "\e907";
}
.icon-close:before {
content: "\e908";
}
.icon-type-References:before {
content: "\e909";
}
.icon-control-Checkbox:before {
content: "\e90a";
}
.icon-control-Dropdown:before {
content: "\e90b";
}
.icon-control-Input:before {
content: "\e90c";
}
.icon-control-Radio:before {
content: "\e90d";
}
.icon-action-Medium:before {
content: "\e959";
}
@ -288,54 +387,6 @@
.icon-action-Webhook:before {
content: "\e947";
}
.icon-hour-glass:before {
content: "\e954";
}
.icon-spinner:before {
content: "\e953";
}
.icon-clock:before {
content: "\e950";
}
.icon-bin2:before {
content: "\e902";
}
.icon-earth:before {
content: "\e9ca";
}
.icon-elapsed:before {
content: "\e943";
}
.icon-google:before {
content: "\e93b";
}
.icon-lock:before {
content: "\e934";
}
.icon-microsoft:before {
content: "\e940";
}
.icon-action-AzureQueue:before {
content: "\e940";
}
.icon-pause:before {
content: "\e92f";
}
.icon-play:before {
content: "\e930";
}
.icon-reset:before {
content: "\e92e";
}
.icon-settings2:before {
content: "\e92d";
}
.icon-timeout:before {
content: "\e944";
}
.icon-unlocked:before {
content: "\e933";
}
.icon-browser:before {
content: "\e935";
}

15
src/Squidex/appsettings.json

@ -237,8 +237,8 @@
*/
"oidcName": "OIDC",
"oidcAuthority": "",
"oidcClient": "",
"oidcSecret": ""
"oidcClient": "",
"oidcSecret": "",
/*
* Lock new users automatically, the administrator must unlock them.
*/
@ -247,5 +247,16 @@
* The url to you privacy statements, if you host squidex by yourself.
*/
"privacyUrl": "https://squidex.io/privacy"
},
"twitter": {
/*
* The client id for twitter.
*/
"clientId": "",
/*
* The client secret for twitter.
*/
"clientSecret": ""
}
}

43
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/TweetActionTests.cs

@ -0,0 +1,43 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
{
public class TweetActionTests
{
[Fact]
public async Task Should_add_error_if_pin_code_is_null()
{
var action = new TweetAction { PinCode = null };
var errors = await RuleActionValidator.ValidateAsync(action);
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Pin Code is required.", "PinCode")
});
}
[Fact]
public async Task Should_not_add_error_if_pin_code_is_absolute()
{
var action = new TweetAction { PinCode = "123" };
var errors = await RuleActionValidator.ValidateAsync(action);
Assert.Empty(errors);
}
}
}
Loading…
Cancel
Save