Async load popular modes into CodeMirror
Dynamically load modes after the type of a file has been discovered from the server. This allows browers to download only the segments of CodeMirror they need to render the current file(s) being viewed. Change-Id: I34c037199cd01bf65b051400999d141eb0524cd8
This commit is contained in:
@@ -85,8 +85,6 @@ import com.google.gwtjsonrpc.client.impl.ResultDeserializer;
|
||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||
import com.google.gwtorm.client.KeyUtil;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Gerrit implements EntryPoint {
|
||||
@@ -368,7 +366,6 @@ public class Gerrit implements EntryPoint {
|
||||
|
||||
initHostname();
|
||||
Window.setTitle(M.windowTitle1(myHost));
|
||||
CodeMirror.install();
|
||||
|
||||
final HostPageDataService hpd = GWT.create(HostPageDataService.class);
|
||||
hpd.load(new GerritCallback<HostPageData>() {
|
||||
|
@@ -14,6 +14,8 @@
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import com.google.gerrit.client.rpc.CallbackGroup;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||
import com.google.gerrit.client.ui.Screen;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
@@ -25,6 +27,7 @@ import com.google.gwt.user.client.ui.FlowPanel;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.ModeInjector;
|
||||
|
||||
public class CodeMirrorDemo extends Screen {
|
||||
private static final int HEADER_FOOTER = 60 + 15 * 2 + 38;
|
||||
@@ -55,16 +58,30 @@ public class CodeMirrorDemo extends Screen {
|
||||
protected void onLoad() {
|
||||
super.onLoad();
|
||||
|
||||
CallbackGroup group = new CallbackGroup();
|
||||
CodeMirror.initLibrary(group.add(new GerritCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
}
|
||||
}));
|
||||
DiffApi.diff(revision, path)
|
||||
.base(base)
|
||||
.wholeFile()
|
||||
.ignoreWhitespace(DiffApi.IgnoreWhitespace.NONE)
|
||||
.get(new ScreenLoadCallback<DiffInfo>(this) {
|
||||
.get(group.add(new GerritCallback<DiffInfo>() {
|
||||
@Override
|
||||
protected void preDisplay(DiffInfo diff) {
|
||||
display(diff);
|
||||
public void onSuccess(final DiffInfo diff) {
|
||||
new ModeInjector()
|
||||
.add(getContentType(diff.meta_a()))
|
||||
.add(getContentType(diff.meta_b()))
|
||||
.inject(new ScreenLoadCallback<Void>(CodeMirrorDemo.this){
|
||||
@Override
|
||||
protected void preDisplay(Void result) {
|
||||
display(diff);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,10 +109,8 @@ public class CodeMirrorDemo extends Screen {
|
||||
.set("readOnly", true)
|
||||
.set("lineNumbers", true)
|
||||
.set("tabSize", 2)
|
||||
.set("mode", getContentType(diff.meta_b()))
|
||||
.set("value", diff.text_b());
|
||||
if (diff.meta_b() != null && diff.meta_b().content_type() != null) {
|
||||
cfg.set("mode", diff.meta_b().content_type());
|
||||
}
|
||||
|
||||
cm = CodeMirror.create(editorContainer.getElement(), cfg);
|
||||
cm.setWidth("100%");
|
||||
@@ -108,4 +123,10 @@ public class CodeMirrorDemo extends Screen {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String getContentType(DiffInfo.FileMeta meta) {
|
||||
return meta != null && meta.content_type() != null
|
||||
? ModeInjector.getContentType(meta.content_type())
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
@@ -17,4 +17,5 @@
|
||||
<inherits name='com.google.gwt.logging.Logging'/>
|
||||
<inherits name='com.google.gwt.resources.Resources'/>
|
||||
<source path='lib'/>
|
||||
<source path='mode'/>
|
||||
</module>
|
||||
|
@@ -15,18 +15,8 @@
|
||||
package net.codemirror.lib;
|
||||
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.dom.client.Document;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.ScriptElement;
|
||||
import com.google.gwt.dom.client.StyleInjector;
|
||||
import com.google.gwt.resources.client.ExternalTextResource;
|
||||
import com.google.gwt.resources.client.ResourceCallback;
|
||||
import com.google.gwt.resources.client.ResourceException;
|
||||
import com.google.gwt.resources.client.TextResource;
|
||||
import com.google.gwt.safehtml.shared.SafeUri;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
|
||||
/**
|
||||
* Glue to connect CodeMirror to be callable from GWT.
|
||||
@@ -34,6 +24,10 @@ import java.util.logging.Logger;
|
||||
* @link http://codemirror.net/doc/manual.html#api
|
||||
*/
|
||||
public class CodeMirror extends JavaScriptObject {
|
||||
public static void initLibrary(AsyncCallback<Void> cb) {
|
||||
Loader.initLibrary(cb);
|
||||
}
|
||||
|
||||
public static native CodeMirror create(Element parent, Configuration cfg) /*-{
|
||||
return $wnd.CodeMirror(parent, cfg);
|
||||
}-*/;
|
||||
@@ -48,42 +42,6 @@ public class CodeMirror extends JavaScriptObject {
|
||||
public final native void refresh() /*-{ this.refresh(); }-*/;
|
||||
public final native Element getWrapperElement() /*-{ return this.getWrapperElement(); }-*/;
|
||||
|
||||
public static void install() {
|
||||
asyncInjectCss(Lib.I.css());
|
||||
asyncInjectScript(Lib.I.js().getSafeUri());
|
||||
}
|
||||
|
||||
private static void asyncInjectCss(ExternalTextResource css) {
|
||||
try {
|
||||
css.getText(new ResourceCallback<TextResource>() {
|
||||
@Override
|
||||
public void onSuccess(TextResource resource) {
|
||||
StyleInjector.inject(resource.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ResourceException e) {
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
} catch (ResourceException e) {
|
||||
error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void asyncInjectScript(SafeUri uri) {
|
||||
ScriptElement script = Document.get().createScriptElement();
|
||||
script.setSrc(uri.asString());
|
||||
script.setLang("javascript");
|
||||
script.setType("text/javascript");
|
||||
Document.get().getBody().appendChild(script);
|
||||
}
|
||||
|
||||
private static void error(ResourceException e) {
|
||||
Logger log = Logger.getLogger("net.codemirror");
|
||||
log.log(Level.SEVERE, "Cannot fetch CSS", e);
|
||||
}
|
||||
|
||||
protected CodeMirror() {
|
||||
}
|
||||
}
|
||||
|
90
gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
vendored
Normal file
90
gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net.codemirror.lib;
|
||||
|
||||
import com.google.gwt.core.client.Callback;
|
||||
import com.google.gwt.core.client.ScriptInjector;
|
||||
import com.google.gwt.dom.client.ScriptElement;
|
||||
import com.google.gwt.dom.client.StyleInjector;
|
||||
import com.google.gwt.resources.client.ExternalTextResource;
|
||||
import com.google.gwt.resources.client.ResourceCallback;
|
||||
import com.google.gwt.resources.client.ResourceException;
|
||||
import com.google.gwt.resources.client.TextResource;
|
||||
import com.google.gwt.safehtml.shared.SafeUri;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class Loader {
|
||||
private static native boolean isLibLoaded()
|
||||
/*-{ return $wnd.hasOwnProperty('CodeMirror'); }-*/;
|
||||
|
||||
static void initLibrary(AsyncCallback<Void> cb) {
|
||||
if (isLibLoaded()) {
|
||||
cb.onSuccess(null);
|
||||
} else {
|
||||
injectCss(Lib.I.css());
|
||||
injectScript(Lib.I.js().getSafeUri(), cb);
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectCss(ExternalTextResource css) {
|
||||
try {
|
||||
css.getText(new ResourceCallback<TextResource>() {
|
||||
@Override
|
||||
public void onSuccess(TextResource resource) {
|
||||
StyleInjector.inject(resource.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ResourceException e) {
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
} catch (ResourceException e) {
|
||||
error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void injectScript(SafeUri js, final AsyncCallback<Void> callback) {
|
||||
final ScriptElement[] script = new ScriptElement[1];
|
||||
script[0] = ScriptInjector.fromUrl(js.asString())
|
||||
.setWindow(ScriptInjector.TOP_WINDOW)
|
||||
.setCallback(new Callback<Void, Exception>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
script[0].removeFromParent();
|
||||
callback.onSuccess(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception reason) {
|
||||
error(reason);
|
||||
callback.onFailure(reason);
|
||||
}
|
||||
})
|
||||
.inject()
|
||||
.cast();
|
||||
}
|
||||
|
||||
private static void error(Exception e) {
|
||||
Logger log = Logger.getLogger("net.codemirror");
|
||||
log.log(Level.SEVERE, "Cannot load portions of CodeMirror", e);
|
||||
}
|
||||
|
||||
private Loader() {
|
||||
}
|
||||
}
|
179
gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
vendored
Normal file
179
gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net.codemirror.lib;
|
||||
|
||||
import com.google.gwt.core.client.JsArrayString;
|
||||
import com.google.gwt.resources.client.DataResource;
|
||||
import com.google.gwt.safehtml.shared.SafeUri;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
|
||||
import net.codemirror.mode.Modes;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ModeInjector {
|
||||
/** Map of server content type to CodeMiror mode or content type. */
|
||||
private static final Map<String, String> mimeAlias;
|
||||
|
||||
/** Map of content type "text/x-java" to mode name "clike". */
|
||||
private static final Map<String, String> mimeModes;
|
||||
|
||||
/** Map of names such as "clike" to URI for code download. */
|
||||
private static final Map<String, SafeUri> modeUris;
|
||||
|
||||
static {
|
||||
DataResource[] all = {
|
||||
Modes.I.clike(),
|
||||
Modes.I.css(),
|
||||
Modes.I.go(),
|
||||
Modes.I.htmlmixed(),
|
||||
Modes.I.javascript(),
|
||||
Modes.I.properties(),
|
||||
Modes.I.python(),
|
||||
Modes.I.shell(),
|
||||
Modes.I.sql(),
|
||||
Modes.I.velocity(),
|
||||
Modes.I.xml(),
|
||||
};
|
||||
|
||||
mimeAlias = new HashMap<String, String>();
|
||||
mimeModes = new HashMap<String, String>();
|
||||
modeUris = new HashMap<String, SafeUri>();
|
||||
|
||||
for (DataResource m : all) {
|
||||
modeUris.put(m.getName(), m.getSafeUri());
|
||||
}
|
||||
parseModeMap();
|
||||
}
|
||||
|
||||
private static void parseModeMap() {
|
||||
String mode = null;
|
||||
for (String line : Modes.I.mode_map().getText().split("\n")) {
|
||||
int eq = line.indexOf('=');
|
||||
if (0 < eq) {
|
||||
mimeAlias.put(
|
||||
line.substring(0, eq).trim(),
|
||||
line.substring(eq + 1).trim());
|
||||
} else if (line.endsWith(":")) {
|
||||
String n = line.substring(0, line.length() - 1);
|
||||
if (modeUris.containsKey(n)) {
|
||||
mode = n;
|
||||
}
|
||||
} else if (mode != null && line.contains("/")) {
|
||||
mimeModes.put(line, mode);
|
||||
} else {
|
||||
mode = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getContentType(String mode) {
|
||||
String real = mode != null ? mimeAlias.get(mode) : null;
|
||||
return real != null ? real : mode;
|
||||
}
|
||||
|
||||
private static native boolean isModeLoaded(String n)
|
||||
/*-{ return $wnd.CodeMirror.modes.hasOwnProperty(n); }-*/;
|
||||
|
||||
private static native boolean isMimeLoaded(String n)
|
||||
/*-{ return $wnd.CodeMirror.mimeModes.hasOwnProperty(n); }-*/;
|
||||
|
||||
private static native JsArrayString getDependencies(String n)
|
||||
/*-{ return $wnd.CodeMirror.modes[n].dependencies || []; }-*/;
|
||||
|
||||
private final Set<String> loading = new HashSet<String>(4);
|
||||
private int pending;
|
||||
private AsyncCallback<Void> appCallback;
|
||||
|
||||
public ModeInjector add(String name) {
|
||||
if (name == null || isModeLoaded(name) || isMimeLoaded(name)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
String mode = mimeModes.get(name);
|
||||
if (mode == null) {
|
||||
mode = name;
|
||||
}
|
||||
|
||||
SafeUri uri = modeUris.get(mode);
|
||||
if (uri == null) {
|
||||
Logger.getLogger("net.codemirror").log(
|
||||
Level.WARNING,
|
||||
"CodeMirror mode " + mode + " not configured.");
|
||||
return this;
|
||||
}
|
||||
|
||||
loading.add(mode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void inject(AsyncCallback<Void> appCallback) {
|
||||
this.appCallback = appCallback;
|
||||
for (String mode : loading) {
|
||||
beginLoading(mode);
|
||||
}
|
||||
if (pending == 0) {
|
||||
appCallback.onSuccess(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void beginLoading(final String mode) {
|
||||
pending++;
|
||||
Loader.injectScript(
|
||||
modeUris.get(mode),
|
||||
new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
pending--;
|
||||
ensureDependenciesAreLoaded(mode);
|
||||
if (pending == 0) {
|
||||
appCallback.onSuccess(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
if (--pending == 0) {
|
||||
appCallback.onFailure(caught);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ensureDependenciesAreLoaded(String mode) {
|
||||
JsArrayString deps = getDependencies(mode);
|
||||
for (int i = 0; i < deps.length(); i++) {
|
||||
String d = deps.get(i);
|
||||
if (loading.contains(d) || isModeLoaded(d)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SafeUri uri = modeUris.get(d);
|
||||
if (uri == null) {
|
||||
Logger.getLogger("net.codemirror").log(
|
||||
Level.SEVERE,
|
||||
"CodeMirror mode " + mode + " needs " + d);
|
||||
continue;
|
||||
}
|
||||
|
||||
beginLoading(d);
|
||||
}
|
||||
}
|
||||
}
|
40
gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
vendored
Normal file
40
gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net.codemirror.mode;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.resources.client.ClientBundle;
|
||||
import com.google.gwt.resources.client.DataResource;
|
||||
import com.google.gwt.resources.client.DataResource.DoNotEmbed;
|
||||
import com.google.gwt.resources.client.TextResource;
|
||||
|
||||
public interface Modes extends ClientBundle {
|
||||
public static final Modes I = GWT.create(Modes.class);
|
||||
|
||||
@Source("mode_map") TextResource mode_map();
|
||||
@Source("clike/clike.js") @DoNotEmbed DataResource clike();
|
||||
@Source("css/css.js") @DoNotEmbed DataResource css();
|
||||
@Source("go/go.js") @DoNotEmbed DataResource go();
|
||||
@Source("htmlmixed/htmlmixed.js") @DoNotEmbed DataResource htmlmixed();
|
||||
@Source("javascript/javascript.js") @DoNotEmbed DataResource javascript();
|
||||
@Source("properties/properties.js") @DoNotEmbed DataResource properties();
|
||||
@Source("python/python.js") @DoNotEmbed DataResource python();
|
||||
@Source("shell/shell.js") @DoNotEmbed DataResource shell();
|
||||
@Source("sql/sql.js") @DoNotEmbed DataResource sql();
|
||||
@Source("velocity/velocity.js") @DoNotEmbed DataResource velocity();
|
||||
@Source("xml/xml.js") @DoNotEmbed DataResource xml();
|
||||
|
||||
// When adding a resource, update static initializer in ModeInjector.
|
||||
}
|
54
gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
vendored
Normal file
54
gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
clike:
|
||||
text/x-csrc
|
||||
text/x-c
|
||||
text/x-chdr
|
||||
text/x-c++src
|
||||
text/x-c++hdr
|
||||
text/x-java
|
||||
text/x-csharp
|
||||
text/x-scala
|
||||
|
||||
css:
|
||||
text/css
|
||||
text/x-scss
|
||||
|
||||
go:
|
||||
text/x-go
|
||||
|
||||
htmlmixed:
|
||||
text/html
|
||||
|
||||
javascript:
|
||||
text/javascript
|
||||
text/ecmascript
|
||||
application/javascript
|
||||
application/ecmascript
|
||||
application/json
|
||||
application/x-json
|
||||
text/typescript
|
||||
application/typescript
|
||||
|
||||
properties:
|
||||
text/x-ini
|
||||
text/x-properties
|
||||
|
||||
python:
|
||||
text/x-python
|
||||
|
||||
shell:
|
||||
text/x-sh
|
||||
|
||||
sql:
|
||||
text/x-sql
|
||||
text/x-mariadb
|
||||
text/x-mysql
|
||||
text/x-plsql
|
||||
|
||||
velocity:
|
||||
text/velocity
|
||||
|
||||
xml:
|
||||
text/xml
|
||||
application/xml
|
||||
|
||||
text/x-java-source = text/x-java
|
@@ -17,3 +17,28 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
----
|
||||
|
||||
codemirror Python mode:
|
||||
----
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010 Timothy Farrell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
Reference in New Issue
Block a user