mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
113 changed files with 2552 additions and 175 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
|||||
|
p41pr8YRXXiqrW5DrLpCxbsyTZwZ9ONcdFjYaHorvCuvPeMiBgl+WOFKkzLENEnJdh22tkh7FKjfSMW1S1jtZg== |
||||
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> |
||||
|
<metadata> |
||||
|
<id>Jurassic</id> |
||||
|
<version>4.0.0</version> |
||||
|
<authors>Paul Bartrum</authors> |
||||
|
<owners>Paul Bartrum</owners> |
||||
|
<requireLicenseAcceptance>false</requireLicenseAcceptance> |
||||
|
<description>A .NET library to parse and execute JavaScript code.</description> |
||||
|
<dependencies> |
||||
|
<group targetFramework=".NETStandard2.0"> |
||||
|
<dependency id="System.Reflection.Emit.Lightweight" version="4.3.0" exclude="Build,Analyzers" /> |
||||
|
</group> |
||||
|
</dependencies> |
||||
|
</metadata> |
||||
|
</package> |
||||
@ -0,0 +1,132 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentDataObject.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
using Jint.Native.Object; |
||||
|
using Jint.Runtime.Descriptors; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public sealed class ContentDataObject : ObjectInstance |
||||
|
{ |
||||
|
private readonly NamedContentData contentData; |
||||
|
private HashSet<string> fieldsToDelete; |
||||
|
private Dictionary<string, ContentDataProperty> fieldProperties; |
||||
|
private bool isChanged; |
||||
|
|
||||
|
public ContentDataObject(Engine engine, NamedContentData contentData) |
||||
|
: base(engine) |
||||
|
{ |
||||
|
Extensible = true; |
||||
|
|
||||
|
this.contentData = contentData; |
||||
|
} |
||||
|
|
||||
|
public void MarkChanged() |
||||
|
{ |
||||
|
isChanged = true; |
||||
|
} |
||||
|
|
||||
|
public bool TryUpdate(out NamedContentData result) |
||||
|
{ |
||||
|
result = contentData; |
||||
|
|
||||
|
if (isChanged) |
||||
|
{ |
||||
|
if (fieldsToDelete != null) |
||||
|
{ |
||||
|
foreach (var field in fieldsToDelete) |
||||
|
{ |
||||
|
contentData.Remove(field); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (fieldProperties != null) |
||||
|
{ |
||||
|
foreach (var kvp in fieldProperties) |
||||
|
{ |
||||
|
if (kvp.Value.ContentField.TryUpdate(out var fieldData)) |
||||
|
{ |
||||
|
contentData[kvp.Key] = fieldData; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return isChanged; |
||||
|
} |
||||
|
|
||||
|
public override void RemoveOwnProperty(string propertyName) |
||||
|
{ |
||||
|
if (fieldsToDelete == null) |
||||
|
{ |
||||
|
fieldsToDelete = new HashSet<string>(); |
||||
|
} |
||||
|
|
||||
|
fieldsToDelete.Add(propertyName); |
||||
|
fieldProperties?.Remove(propertyName); |
||||
|
|
||||
|
MarkChanged(); |
||||
|
} |
||||
|
|
||||
|
public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
if (!fieldProperties.ContainsKey(propertyName)) |
||||
|
{ |
||||
|
fieldProperties[propertyName] = new ContentDataProperty(this) { Value = desc.Value }; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public override void Put(string propertyName, JsValue value, bool throwOnError) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
fieldProperties.GetOrAdd(propertyName, x => new ContentDataProperty(this)).Value = value; |
||||
|
} |
||||
|
|
||||
|
public override PropertyDescriptor GetOwnProperty(string propertyName) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
return fieldProperties.GetOrDefault(propertyName) ?? new PropertyDescriptor(new ObjectInstance(Engine) { Extensible = true }, true, false, true); |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties() |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
foreach (var property in fieldProperties) |
||||
|
{ |
||||
|
yield return new KeyValuePair<string, PropertyDescriptor>(property.Key, property.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void EnsurePropertiesInitialized() |
||||
|
{ |
||||
|
if (fieldProperties == null) |
||||
|
{ |
||||
|
fieldProperties = new Dictionary<string, ContentDataProperty>(contentData.Count); |
||||
|
|
||||
|
foreach (var kvp in contentData) |
||||
|
{ |
||||
|
fieldProperties.Add(kvp.Key, new ContentDataProperty(this, new ContentFieldObject(this, kvp.Value, false))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentFieldProperty.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint.Native; |
||||
|
using Jint.Runtime; |
||||
|
using Jint.Runtime.Descriptors; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public sealed class ContentDataProperty : PropertyDescriptor |
||||
|
{ |
||||
|
private readonly ContentDataObject contentData; |
||||
|
private ContentFieldObject contentField; |
||||
|
private JsValue value; |
||||
|
|
||||
|
public override JsValue Value |
||||
|
{ |
||||
|
get { return value; } |
||||
|
set |
||||
|
{ |
||||
|
if (!Equals(this.value, value)) |
||||
|
{ |
||||
|
if (value == null || !value.IsObject()) |
||||
|
{ |
||||
|
throw new JavaScriptException("Can only assign object to content data."); |
||||
|
} |
||||
|
|
||||
|
var obj = value.AsObject(); |
||||
|
|
||||
|
contentField = new ContentFieldObject(contentData, new ContentFieldData(), true); |
||||
|
|
||||
|
foreach (var kvp in obj.GetOwnProperties()) |
||||
|
{ |
||||
|
contentField.Put(kvp.Key, kvp.Value.Value, true); |
||||
|
} |
||||
|
|
||||
|
this.value = new JsValue(contentField); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ContentFieldObject ContentField |
||||
|
{ |
||||
|
get { return contentField; } |
||||
|
} |
||||
|
|
||||
|
public ContentDataProperty(ContentDataObject contentData, ContentFieldObject contentField = null) |
||||
|
: base(null, true, true, true) |
||||
|
{ |
||||
|
this.contentData = contentData; |
||||
|
this.contentField = contentField; |
||||
|
|
||||
|
if (contentField != null) |
||||
|
{ |
||||
|
value = new JsValue(contentField); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,142 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentFieldObject.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Jint.Native.Object; |
||||
|
using Jint.Runtime.Descriptors; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public sealed class ContentFieldObject : ObjectInstance |
||||
|
{ |
||||
|
private readonly ContentDataObject contentData; |
||||
|
private readonly ContentFieldData fieldData; |
||||
|
private HashSet<string> valuesToDelete; |
||||
|
private Dictionary<string, ContentFieldProperty> valueProperties; |
||||
|
private bool isChanged; |
||||
|
|
||||
|
public bool IsChanged |
||||
|
{ |
||||
|
get { return isChanged; } |
||||
|
} |
||||
|
|
||||
|
public ContentFieldData FieldData |
||||
|
{ |
||||
|
get { return fieldData; } |
||||
|
} |
||||
|
|
||||
|
public ContentFieldObject(ContentDataObject contentData, ContentFieldData fieldData, bool isNew) |
||||
|
: base(contentData.Engine) |
||||
|
{ |
||||
|
Extensible = true; |
||||
|
|
||||
|
this.contentData = contentData; |
||||
|
this.fieldData = fieldData; |
||||
|
|
||||
|
if (isNew) |
||||
|
{ |
||||
|
MarkChanged(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void MarkChanged() |
||||
|
{ |
||||
|
isChanged = true; |
||||
|
|
||||
|
contentData.MarkChanged(); |
||||
|
} |
||||
|
|
||||
|
public bool TryUpdate(out ContentFieldData result) |
||||
|
{ |
||||
|
result = fieldData; |
||||
|
|
||||
|
if (isChanged) |
||||
|
{ |
||||
|
if (valuesToDelete != null) |
||||
|
{ |
||||
|
foreach (var field in valuesToDelete) |
||||
|
{ |
||||
|
fieldData.Remove(field); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (valueProperties != null) |
||||
|
{ |
||||
|
foreach (var kvp in valueProperties) |
||||
|
{ |
||||
|
if (kvp.Value.IsChanged) |
||||
|
{ |
||||
|
fieldData[kvp.Key] = kvp.Value.ContentValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return isChanged; |
||||
|
} |
||||
|
|
||||
|
public override void RemoveOwnProperty(string propertyName) |
||||
|
{ |
||||
|
if (valuesToDelete == null) |
||||
|
{ |
||||
|
valuesToDelete = new HashSet<string>(); |
||||
|
} |
||||
|
|
||||
|
valuesToDelete.Add(propertyName); |
||||
|
valueProperties?.Remove(propertyName); |
||||
|
|
||||
|
MarkChanged(); |
||||
|
} |
||||
|
|
||||
|
public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
if (!valueProperties.ContainsKey(propertyName)) |
||||
|
{ |
||||
|
valueProperties[propertyName] = new ContentFieldProperty(this) { Value = desc.Value }; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public override PropertyDescriptor GetOwnProperty(string propertyName) |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
return valueProperties?.GetOrDefault(propertyName) ?? PropertyDescriptor.Undefined; |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<KeyValuePair<string, PropertyDescriptor>> GetOwnProperties() |
||||
|
{ |
||||
|
EnsurePropertiesInitialized(); |
||||
|
|
||||
|
foreach (var property in valueProperties) |
||||
|
{ |
||||
|
yield return new KeyValuePair<string, PropertyDescriptor>(property.Key, property.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void EnsurePropertiesInitialized() |
||||
|
{ |
||||
|
if (valueProperties == null) |
||||
|
{ |
||||
|
valueProperties = new Dictionary<string, ContentFieldProperty>(FieldData.Count); |
||||
|
|
||||
|
foreach (var kvp in FieldData) |
||||
|
{ |
||||
|
valueProperties.Add(kvp.Key, new ContentFieldProperty(this, kvp.Value)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentFieldProperty.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Jint.Native; |
||||
|
using Jint.Runtime.Descriptors; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public sealed class ContentFieldProperty : PropertyDescriptor |
||||
|
{ |
||||
|
private readonly ContentFieldObject contentField; |
||||
|
private JToken contentValue; |
||||
|
private JsValue value; |
||||
|
private bool isChanged; |
||||
|
|
||||
|
public override JsValue Value |
||||
|
{ |
||||
|
get { return value ?? (value = JsonMapper.Map(contentValue, contentField.Engine)); } |
||||
|
set |
||||
|
{ |
||||
|
if (!Equals(this.value, value)) |
||||
|
{ |
||||
|
this.value = value; |
||||
|
|
||||
|
contentValue = null; |
||||
|
contentField.MarkChanged(); |
||||
|
|
||||
|
isChanged = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public JToken ContentValue |
||||
|
{ |
||||
|
get { return contentValue ?? (contentValue = JsonMapper.Map(value)); } |
||||
|
} |
||||
|
|
||||
|
public bool IsChanged |
||||
|
{ |
||||
|
get { return isChanged; } |
||||
|
} |
||||
|
|
||||
|
public ContentFieldProperty(ContentFieldObject contentField, JToken contentValue = null) |
||||
|
: base(null, true, true, true) |
||||
|
{ |
||||
|
this.contentField = contentField; |
||||
|
this.contentValue = contentValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JsonMapper.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
using Jint.Native.Object; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
// ReSharper disable SwitchStatementMissingSomeCases
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper |
||||
|
{ |
||||
|
public static class JsonMapper |
||||
|
{ |
||||
|
public static JsValue Map(JToken value, Engine engine) |
||||
|
{ |
||||
|
if (value == null) |
||||
|
{ |
||||
|
return JsValue.Null; |
||||
|
} |
||||
|
|
||||
|
switch (value.Type) |
||||
|
{ |
||||
|
case JTokenType.Date: |
||||
|
case JTokenType.Guid: |
||||
|
case JTokenType.String: |
||||
|
case JTokenType.Uri: |
||||
|
case JTokenType.TimeSpan: |
||||
|
return new JsValue((string)value); |
||||
|
case JTokenType.Null: |
||||
|
return JsValue.Null; |
||||
|
case JTokenType.Undefined: |
||||
|
return JsValue.Undefined; |
||||
|
case JTokenType.Integer: |
||||
|
return new JsValue((long)value); |
||||
|
case JTokenType.Float: |
||||
|
return new JsValue((double)value); |
||||
|
case JTokenType.Boolean: |
||||
|
return new JsValue((bool)value); |
||||
|
case JTokenType.Object: |
||||
|
{ |
||||
|
var obj = (JObject)value; |
||||
|
|
||||
|
var target = new ObjectInstance(engine); |
||||
|
|
||||
|
foreach (var property in obj) |
||||
|
{ |
||||
|
target.FastAddProperty(property.Key, Map(property.Value, engine), false, true, true); |
||||
|
} |
||||
|
|
||||
|
return target; |
||||
|
} |
||||
|
case JTokenType.Array: |
||||
|
{ |
||||
|
var arr = (JArray)value; |
||||
|
|
||||
|
var target = new JsValue[arr.Count]; |
||||
|
|
||||
|
for (var i = 0; i < arr.Count; i++) |
||||
|
{ |
||||
|
target[i] = Map(arr[i], engine); |
||||
|
} |
||||
|
|
||||
|
return engine.Array.Construct(target); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new ArgumentException("Invalid json type", nameof(value)); |
||||
|
} |
||||
|
|
||||
|
public static JToken Map(JsValue value) |
||||
|
{ |
||||
|
if (value == null || value.IsNull()) |
||||
|
{ |
||||
|
return JValue.CreateNull(); |
||||
|
} |
||||
|
|
||||
|
if (value.IsUndefined()) |
||||
|
{ |
||||
|
return JValue.CreateUndefined(); |
||||
|
} |
||||
|
|
||||
|
if (value.IsString()) |
||||
|
{ |
||||
|
return new JValue(value.AsString()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsBoolean()) |
||||
|
{ |
||||
|
return new JValue(value.AsBoolean()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsNumber()) |
||||
|
{ |
||||
|
return new JValue(value.AsNumber()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsDate()) |
||||
|
{ |
||||
|
return new JValue(value.AsDate().ToDateTime()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsRegExp()) |
||||
|
{ |
||||
|
return JValue.CreateString(value.AsRegExp().Value?.ToString()); |
||||
|
} |
||||
|
|
||||
|
if (value.IsArray()) |
||||
|
{ |
||||
|
var arr = value.AsArray(); |
||||
|
|
||||
|
var target = new JArray(); |
||||
|
|
||||
|
for (var i = 0; i < arr.GetLength(); i++) |
||||
|
{ |
||||
|
target.Add(Map(arr.Get(i.ToString()))); |
||||
|
} |
||||
|
|
||||
|
return target; |
||||
|
} |
||||
|
|
||||
|
if (value.IsObject()) |
||||
|
{ |
||||
|
var obj = value.AsObject(); |
||||
|
|
||||
|
var target = new JObject(); |
||||
|
|
||||
|
foreach (var kvp in obj.GetOwnProperties()) |
||||
|
{ |
||||
|
target[kvp.Key] = Map(kvp.Value.Value); |
||||
|
} |
||||
|
|
||||
|
return target; |
||||
|
} |
||||
|
|
||||
|
throw new ArgumentException("Invalid json type", nameof(value)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IScriptEngine.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public interface IScriptEngine |
||||
|
{ |
||||
|
void Execute(ScriptContext context, string script, string operationName); |
||||
|
|
||||
|
NamedContentData ExecuteAndTransform(ScriptContext context, string script, string operationName); |
||||
|
|
||||
|
NamedContentData Transform(ScriptContext context, string script); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,167 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JintScriptEngine.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Jint; |
||||
|
using Jint.Native.Object; |
||||
|
using Jint.Parser; |
||||
|
using Jint.Runtime; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Scripting.ContentWrapper; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public sealed class JintScriptEngine : IScriptEngine |
||||
|
{ |
||||
|
public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(200); |
||||
|
|
||||
|
public void Execute(ScriptContext context, string script, string operationName) |
||||
|
{ |
||||
|
Guard.NotNull(context, nameof(context)); |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(script)) |
||||
|
{ |
||||
|
var engine = CreateScriptEngine(context); |
||||
|
|
||||
|
EnableDisallow(engine); |
||||
|
EnableReject(engine, operationName); |
||||
|
|
||||
|
Execute(engine, script, operationName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public NamedContentData ExecuteAndTransform(ScriptContext context, string script, string operationName) |
||||
|
{ |
||||
|
Guard.NotNull(context, nameof(context)); |
||||
|
|
||||
|
var result = context.Data; |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(script)) |
||||
|
{ |
||||
|
var engine = CreateScriptEngine(context); |
||||
|
|
||||
|
EnableDisallow(engine); |
||||
|
EnableReject(engine, operationName); |
||||
|
|
||||
|
engine.SetValue("replace", new Action(() => |
||||
|
{ |
||||
|
var dataInstance = engine.GetValue("ctx").AsObject().Get("data"); |
||||
|
|
||||
|
if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data) |
||||
|
{ |
||||
|
data.TryUpdate(out result); |
||||
|
} |
||||
|
})); |
||||
|
|
||||
|
Execute(engine, script, operationName); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public NamedContentData Transform(ScriptContext context, string script) |
||||
|
{ |
||||
|
Guard.NotNull(context, nameof(context)); |
||||
|
|
||||
|
var result = context.Data; |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(script)) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var engine = CreateScriptEngine(context); |
||||
|
|
||||
|
engine.SetValue("replace", new Action(() => |
||||
|
{ |
||||
|
var dataInstance = engine.GetValue("ctx").AsObject().Get("data"); |
||||
|
|
||||
|
if (dataInstance != null && dataInstance.IsObject() && dataInstance.AsObject() is ContentDataObject data) |
||||
|
{ |
||||
|
data.TryUpdate(out result); |
||||
|
} |
||||
|
})); |
||||
|
|
||||
|
engine.Execute(script); |
||||
|
} |
||||
|
catch (Exception) |
||||
|
{ |
||||
|
result = context.Data; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private static void Execute(Engine engine, string script, string operationName) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
engine.Execute(script); |
||||
|
} |
||||
|
catch (ParserException ex) |
||||
|
{ |
||||
|
throw new ValidationException($"Failed to {operationName} with javascript syntaxs error.", new ValidationError(ex.Message)); |
||||
|
} |
||||
|
catch (JavaScriptException ex) |
||||
|
{ |
||||
|
throw new ValidationException($"Failed to {operationName} with javascript error.", new ValidationError(ex.Message)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Engine CreateScriptEngine(ScriptContext context) |
||||
|
{ |
||||
|
var engine = new Engine(options => options.TimeoutInterval(Timeout).Strict()); |
||||
|
|
||||
|
var contextInstance = new ObjectInstance(engine); |
||||
|
|
||||
|
if (context.Data != null) |
||||
|
{ |
||||
|
contextInstance.FastAddProperty("data", new ContentDataObject(engine, context.Data), true, true, true); |
||||
|
} |
||||
|
|
||||
|
if (context.OldData != null) |
||||
|
{ |
||||
|
contextInstance.FastAddProperty("oldData", new ContentDataObject(engine, context.OldData), true, true, true); |
||||
|
} |
||||
|
|
||||
|
if (context.User != null) |
||||
|
{ |
||||
|
contextInstance.FastAddProperty("user", new JintUser(engine, context.User), false, true, false); |
||||
|
} |
||||
|
|
||||
|
engine.SetValue("ctx", contextInstance); |
||||
|
|
||||
|
return engine; |
||||
|
} |
||||
|
|
||||
|
private static void EnableDisallow(Engine engine) |
||||
|
{ |
||||
|
engine.SetValue("disallow", new Action<string>(message => |
||||
|
{ |
||||
|
var exMessage = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed"; |
||||
|
|
||||
|
throw new DomainForbiddenException(exMessage); |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
private static void EnableReject(Engine engine, string operationName) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(operationName, nameof(operationName)); |
||||
|
|
||||
|
engine.SetValue("reject", new Action<string>(message => |
||||
|
{ |
||||
|
var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null; |
||||
|
|
||||
|
throw new ValidationException($"Script rejected to {operationName}.", errors); |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JintUser.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using Jint; |
||||
|
using Jint.Native; |
||||
|
using Jint.Native.Object; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public sealed class JintUser : ObjectInstance |
||||
|
{ |
||||
|
public JintUser(Engine engine, ClaimsPrincipal principal) |
||||
|
: base(engine) |
||||
|
{ |
||||
|
var subjectId = principal.OpenIdSubject(); |
||||
|
|
||||
|
var isClient = string.IsNullOrWhiteSpace(subjectId); |
||||
|
|
||||
|
if (!isClient) |
||||
|
{ |
||||
|
FastAddProperty("id", subjectId, false, true, false); |
||||
|
FastAddProperty("isClient", false, false, true, false); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
FastAddProperty("id", principal.OpenIdClientId(), false, true, false); |
||||
|
FastAddProperty("isClient", true, false, true, false); |
||||
|
} |
||||
|
|
||||
|
FastAddProperty("email", principal.OpenIdEmail(), false, true, false); |
||||
|
|
||||
|
var claimsInstance = new ObjectInstance(engine); |
||||
|
|
||||
|
foreach (var group in principal.Claims.GroupBy(x => x.Type)) |
||||
|
{ |
||||
|
claimsInstance.FastAddProperty(group.Key, engine.Array.Construct(group.Select(x => new JsValue(x.Value)).ToArray()), false, true, false); |
||||
|
} |
||||
|
|
||||
|
FastAddProperty("claims", claimsInstance, false, true, false); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ScriptContext.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Security.Claims; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Scripting |
||||
|
{ |
||||
|
public sealed class ScriptContext |
||||
|
{ |
||||
|
public ClaimsPrincipal User { get; set; } |
||||
|
|
||||
|
public Guid ContentId { get; set; } |
||||
|
|
||||
|
public NamedContentData Data { get; set; } |
||||
|
|
||||
|
public NamedContentData OldData { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ScriptsConfigured.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Schemas |
||||
|
{ |
||||
|
[TypeName("ScriptsConfiguredEvent")] |
||||
|
public sealed class ScriptsConfigured : SchemaEvent |
||||
|
{ |
||||
|
public string ScriptQuery { get; set; } |
||||
|
|
||||
|
public string ScriptCreate { get; set; } |
||||
|
|
||||
|
public string ScriptUpdate { get; set; } |
||||
|
|
||||
|
public string ScriptDelete { get; set; } |
||||
|
|
||||
|
public string ScriptPublish { get; set; } |
||||
|
|
||||
|
public string ScriptUnpublish { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentChangedResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Contents |
||||
|
{ |
||||
|
public sealed class ContentDataChangedResult : EntitySavedResult |
||||
|
{ |
||||
|
public NamedContentData Data { get; } |
||||
|
|
||||
|
public ContentDataChangedResult(NamedContentData data, long version) |
||||
|
: base(version) |
||||
|
{ |
||||
|
Data = data; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ConfigureScripts.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Schemas.Commands |
||||
|
{ |
||||
|
public sealed class ConfigureScripts : SchemaAggregateCommand |
||||
|
{ |
||||
|
public string ScriptQuery { get; set; } |
||||
|
|
||||
|
public string ScriptCreate { get; set; } |
||||
|
|
||||
|
public string ScriptUpdate { get; set; } |
||||
|
|
||||
|
public string ScriptDelete { get; set; } |
||||
|
|
||||
|
public string ScriptPublish { get; set; } |
||||
|
|
||||
|
public string ScriptUnpublish { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// ==========================================================================
|
||||
|
// UserClaimsPrincipalFactoryWithEmail.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Squidex.Infrastructure.Security; |
||||
|
using Squidex.Shared.Users; |
||||
|
|
||||
|
namespace Squidex.Domain.Users |
||||
|
{ |
||||
|
public sealed class UserClaimsPrincipalFactoryWithEmail : UserClaimsPrincipalFactory<IUser, IRole> |
||||
|
{ |
||||
|
public UserClaimsPrincipalFactoryWithEmail(UserManager<IUser> userManager, RoleManager<IRole> roleManager, IOptions<IdentityOptions> optionsAccessor) |
||||
|
: base(userManager, roleManager, optionsAccessor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override async Task<ClaimsPrincipal> CreateAsync(IUser user) |
||||
|
{ |
||||
|
var principal = await base.CreateAsync(user); |
||||
|
|
||||
|
principal.Identities.First().AddClaim(new Claim(OpenIdClaims.Email, await UserManager.GetEmailAsync(user))); |
||||
|
|
||||
|
return principal; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DomainForbiddenException.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure |
||||
|
{ |
||||
|
public class DomainForbiddenException : DomainException |
||||
|
{ |
||||
|
public DomainForbiddenException(string message) |
||||
|
: base(message) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public DomainForbiddenException(string message, Exception inner) |
||||
|
: base(message, inner) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ConfigureScriptsDto.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Schemas.Models |
||||
|
{ |
||||
|
public sealed class ConfigureScriptsDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The script that is executed for each query when querying contents.
|
||||
|
/// </summary>
|
||||
|
public string ScriptQuery { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The script that is executed when creating a content.
|
||||
|
/// </summary>
|
||||
|
public string ScriptCreate { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The script that is executed when updating a content.
|
||||
|
/// </summary>
|
||||
|
public string ScriptUpdate { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The script that is executed when deleting a content.
|
||||
|
/// </summary>
|
||||
|
public string ScriptDelete { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The script that is executed when publishing a content.
|
||||
|
/// </summary>
|
||||
|
public string ScriptPublish { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The script that is executed when unpublishing a content.
|
||||
|
/// </summary>
|
||||
|
public string ScriptUnpublish { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
<form [formGroup]="editForm" (ngSubmit)="saveSchema()"> |
||||
|
<div class="modal-header"> |
||||
|
<h4 class="modal-title">Scripts</h4> |
||||
|
|
||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="cancel()"> |
||||
|
<span aria-hidden="true">×</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div class="modal-body"> |
||||
|
<ul class="nav nav-tabs"> |
||||
|
<li class="nav-item" *ngFor="let script of scripts"> |
||||
|
<a class="nav-link" [class.active]="selectedField === 'script' + script" (click)="selectField('script' + script)">{{script}}</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
|
||||
|
<div class="form-group"> |
||||
|
<div *ngFor="let script of scripts"> |
||||
|
<div *ngIf="selectedField === 'script' + script"> |
||||
|
<sqx-jscript-editor name="script" [formControlName]="'script' + script"></sqx-jscript-editor> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="modal-footer"> |
||||
|
<div class="clearfix"> |
||||
|
<button type="reset" class="float-left btn btn-secondary" (click)="cancel()" [disabled]="editFormSubmitted">Cancel</button> |
||||
|
<button type="submit" class="float-right btn btn-primary">Save</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
|
||||
@ -0,0 +1,14 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
|
|
||||
|
.nav-link { |
||||
|
cursor: default; |
||||
|
} |
||||
|
|
||||
|
.nav-tabs { |
||||
|
border: 0; |
||||
|
} |
||||
|
|
||||
|
.clearfix { |
||||
|
width: 100%; |
||||
|
} |
||||
@ -0,0 +1,123 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; |
||||
|
import { FormBuilder } from '@angular/forms'; |
||||
|
|
||||
|
import { |
||||
|
ComponentBase, |
||||
|
DialogService, |
||||
|
SchemaDetailsDto, |
||||
|
SchemasService, |
||||
|
UpdateSchemaScriptsDto |
||||
|
} from 'shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-schema-scripts-form', |
||||
|
styleUrls: ['./schema-scripts-form.component.scss'], |
||||
|
templateUrl: './schema-scripts-form.component.html' |
||||
|
}) |
||||
|
export class SchemaScriptsFormComponent extends ComponentBase implements OnInit { |
||||
|
@Output() |
||||
|
public saved = new EventEmitter<UpdateSchemaScriptsDto>(); |
||||
|
|
||||
|
@Output() |
||||
|
public cancelled = new EventEmitter(); |
||||
|
|
||||
|
@Input() |
||||
|
public schema: SchemaDetailsDto; |
||||
|
|
||||
|
@Input() |
||||
|
public appName: string; |
||||
|
|
||||
|
public selectedField = 'scriptQuery'; |
||||
|
|
||||
|
public scripts = [ |
||||
|
'Query', |
||||
|
'Create', |
||||
|
'Update', |
||||
|
'Delete', |
||||
|
'Publish', |
||||
|
'Unpublish' |
||||
|
]; |
||||
|
|
||||
|
public editFormSubmitted = false; |
||||
|
public editForm = |
||||
|
this.formBuilder.group({ |
||||
|
scriptQuery: '', |
||||
|
scriptCreate: '', |
||||
|
scriptUpdate: '', |
||||
|
scriptDelete: '', |
||||
|
scriptPublish: '', |
||||
|
scriptUnpublish: '' |
||||
|
}); |
||||
|
|
||||
|
constructor(dialogs: DialogService, |
||||
|
private readonly schemas: SchemasService, |
||||
|
private readonly formBuilder: FormBuilder |
||||
|
) { |
||||
|
super(dialogs); |
||||
|
} |
||||
|
|
||||
|
public ngOnInit() { |
||||
|
this.editForm.patchValue(this.schema); |
||||
|
} |
||||
|
|
||||
|
public cancel() { |
||||
|
this.emitCancelled(); |
||||
|
this.resetEditForm(); |
||||
|
} |
||||
|
|
||||
|
public saveSchema() { |
||||
|
this.editFormSubmitted = true; |
||||
|
|
||||
|
if (this.editForm.valid) { |
||||
|
this.editForm.disable(); |
||||
|
|
||||
|
const requestDto = |
||||
|
new UpdateSchemaScriptsDto( |
||||
|
this.editForm.controls['scriptQuery'].value, |
||||
|
this.editForm.controls['scriptCreate'].value, |
||||
|
this.editForm.controls['scriptUpdate'].value, |
||||
|
this.editForm.controls['scriptDelete'].value, |
||||
|
this.editForm.controls['scriptPublish'].value, |
||||
|
this.editForm.controls['scriptUnpublish'].value); |
||||
|
|
||||
|
this.schemas.putSchemaScripts(this.appName, this.schema.name, requestDto, this.schema.version) |
||||
|
.subscribe(dto => { |
||||
|
this.emitSaved(requestDto); |
||||
|
this.resetEditForm(); |
||||
|
}, error => { |
||||
|
this.notifyError(error); |
||||
|
this.enableEditForm(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private emitCancelled() { |
||||
|
this.cancelled.emit(); |
||||
|
} |
||||
|
|
||||
|
private emitSaved(requestDto: UpdateSchemaScriptsDto) { |
||||
|
this.saved.emit(requestDto); |
||||
|
} |
||||
|
|
||||
|
public selectField(field: string) { |
||||
|
this.selectedField = field; |
||||
|
} |
||||
|
|
||||
|
private enableEditForm() { |
||||
|
this.editForm.enable(); |
||||
|
this.editFormSubmitted = false; |
||||
|
} |
||||
|
|
||||
|
private resetEditForm() { |
||||
|
this.editForm.reset(); |
||||
|
this.editForm.enable(); |
||||
|
this.editFormSubmitted = false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
<div class="editor" #editor></div> |
||||
@ -0,0 +1,8 @@ |
|||||
|
@import '_mixins'; |
||||
|
@import '_vars'; |
||||
|
|
||||
|
.editor { |
||||
|
background: $color-dark-foreground; |
||||
|
border: 1px solid $color-input; |
||||
|
height: 20rem; |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { AfterViewInit, Component, forwardRef, ElementRef, ViewChild } from '@angular/core'; |
||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
|
||||
|
import { ResourceLoaderService } from './../services/resource-loader.service'; |
||||
|
|
||||
|
declare var ace: any; |
||||
|
|
||||
|
const NOOP = () => { /* NOOP */ }; |
||||
|
|
||||
|
export const SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR: any = { |
||||
|
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => JscriptEditorComponent), multi: true |
||||
|
}; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-jscript-editor', |
||||
|
styleUrls: ['./jscript-editor.component.scss'], |
||||
|
templateUrl: './jscript-editor.component.html', |
||||
|
providers: [SQX_JSCRIPT_EDITOR_CONTROL_VALUE_ACCESSOR] |
||||
|
}) |
||||
|
export class JscriptEditorComponent implements ControlValueAccessor, AfterViewInit { |
||||
|
private changeCallback: (value: any) => void = NOOP; |
||||
|
private touchedCallback: () => void = NOOP; |
||||
|
private valueChanged = new Subject(); |
||||
|
private aceEditor: any; |
||||
|
private oldValue: string; |
||||
|
private isDisabled = false; |
||||
|
|
||||
|
@ViewChild('editor') |
||||
|
public editor: ElementRef; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly resourceLoader: ResourceLoaderService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public writeValue(value: any) { |
||||
|
this.oldValue = value; |
||||
|
|
||||
|
if (this.aceEditor) { |
||||
|
this.setValue(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public setDisabledState(isDisabled: boolean): void { |
||||
|
this.isDisabled = isDisabled; |
||||
|
|
||||
|
if (this.aceEditor) { |
||||
|
this.aceEditor.setReadOnly(isDisabled); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public registerOnChange(fn: any) { |
||||
|
this.changeCallback = fn; |
||||
|
} |
||||
|
|
||||
|
public registerOnTouched(fn: any) { |
||||
|
this.touchedCallback = fn; |
||||
|
} |
||||
|
|
||||
|
public ngAfterViewInit() { |
||||
|
this.valueChanged.debounceTime(500) |
||||
|
.subscribe(() => { |
||||
|
this.changeValue(); |
||||
|
}); |
||||
|
|
||||
|
this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js').then(() => { |
||||
|
this.aceEditor = ace.edit(this.editor.nativeElement); |
||||
|
|
||||
|
this.aceEditor.getSession().setMode('ace/mode/javascript'); |
||||
|
this.aceEditor.setReadOnly(this.isDisabled); |
||||
|
this.aceEditor.setFontSize(14); |
||||
|
|
||||
|
this.setValue(this.oldValue); |
||||
|
|
||||
|
this.aceEditor.on('blur', () => { |
||||
|
this.changeValue(); |
||||
|
this.touchedCallback(); |
||||
|
}); |
||||
|
|
||||
|
this.aceEditor.on('change', () => { |
||||
|
this.valueChanged.next(); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private changeValue() { |
||||
|
const newValue = this.aceEditor.getValue(); |
||||
|
|
||||
|
if (this.oldValue !== newValue) { |
||||
|
this.changeCallback(newValue); |
||||
|
} |
||||
|
|
||||
|
this.oldValue = newValue; |
||||
|
} |
||||
|
|
||||
|
private setValue(value: string) { |
||||
|
this.aceEditor.setValue(value || ''); |
||||
|
this.aceEditor.clearSelection(); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue