mirror of
https://github.com/GrammaticalFramework/gf-core.git
synced 2026-04-22 19:22:50 -06:00
FridgeApp and TranslateApp now show the type errors
This commit is contained in:
@@ -336,11 +336,11 @@ function target_lang() {
|
|||||||
return langpart(to_menu.options[to_menu.selectedIndex].value,grammar.name);
|
return langpart(to_menu.options[to_menu.selectedIndex].value,grammar.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function show_translations(translations) {
|
function show_translations(translationResults) {
|
||||||
var trans=element("translations");
|
var trans=element("translations");
|
||||||
var grammar=element("language_menu").grammar;
|
var grammar=element("language_menu").grammar;
|
||||||
var to=target_lang();
|
var to=target_lang();
|
||||||
var cnt=translations.length;
|
var cnt=translationResults.length;
|
||||||
//trans.translations=translations;
|
//trans.translations=translations;
|
||||||
trans.single_translation=[];
|
trans.single_translation=[];
|
||||||
trans.innerHTML="";
|
trans.innerHTML="";
|
||||||
@@ -348,18 +348,23 @@ function show_translations(translations) {
|
|||||||
cnt>1 ? ""+cnt+" translations:":
|
cnt>1 ? ""+cnt+" translations:":
|
||||||
"One translation:")));
|
"One translation:")));
|
||||||
for(p=0;p<cnt;p++) {
|
for(p=0;p<cnt;p++) {
|
||||||
var t=translations[p];
|
var tr=translationResults[p];
|
||||||
var lin=t.linearizations;
|
if (tr.translations != null) {
|
||||||
var tbody=empty("tbody");
|
for (q = 0; q < tr.translations.length; q++) {
|
||||||
if(options.show_abstract && t.tree)
|
var t = tr.translations[q];
|
||||||
tbody.appendChild(tr([th(text("Abstract: ")),
|
var lin=t.linearizations;
|
||||||
tdt(abstree_button(t.tree),text(" "+t.tree))]));
|
var tbody=empty("tbody");
|
||||||
for(var i=0;i<lin.length;i++)
|
if(options.show_abstract && t.tree)
|
||||||
if(to=="-1" || lin[i].to==to)
|
tbody.appendChild(tr([th(text("Abstract: ")),
|
||||||
tbody.appendChild(tr([th(text(langpart(lin[i].to,grammar.name)+": ")),
|
tdt(abstree_button(t.tree),text(" "+t.tree))]));
|
||||||
tdt(parsetree_button(t.tree,lin[i].to),
|
for(var i=0;i<lin.length;i++)
|
||||||
text(lin[i].text))]));
|
if(to=="-1" || lin[i].to==to)
|
||||||
trans.appendChild(wrap("table",tbody));
|
tbody.appendChild(tr([th(text(langpart(lin[i].to,grammar.name)+": ")),
|
||||||
|
tdt(parsetree_button(t.tree,lin[i].to),
|
||||||
|
text(lin[i].text))]));
|
||||||
|
trans.appendChild(wrap("table",tbody));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ import System.IO
|
|||||||
logFile :: FilePath
|
logFile :: FilePath
|
||||||
logFile = "pgf-error.log"
|
logFile = "pgf-error.log"
|
||||||
|
|
||||||
--canParse = PGF.canParse -- old
|
|
||||||
canParse _ _ = True -- parser is not optional in new PGF format
|
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do stderrToFile logFile
|
main = do stderrToFile logFile
|
||||||
@@ -116,15 +114,21 @@ pgfMain pgf command =
|
|||||||
doTranslate :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> Maybe PGF.Language -> JSValue
|
doTranslate :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> Maybe PGF.Language -> JSValue
|
||||||
doTranslate pgf input mcat mfrom mto =
|
doTranslate pgf input mcat mfrom mto =
|
||||||
showJSON
|
showJSON
|
||||||
[toJSObject [("from", showJSON (PGF.showLanguage from)),
|
[toJSObject (("from", showJSON from) :
|
||||||
("tree", showJSON tree),
|
("brackets", showJSON bs) :
|
||||||
("linearizations",showJSON
|
jsonParseOutput po)
|
||||||
[toJSObject [("to", PGF.showLanguage to),("text",output)]
|
| (from,po,bs) <- parse' pgf input mcat mfrom]
|
||||||
| (to,output) <- linearizeAndBind pgf mto tree]
|
where
|
||||||
)
|
jsonParseOutput (PGF.ParseOk trees) = [("translations",showJSON
|
||||||
]
|
[toJSObject [("tree", showJSON tree),
|
||||||
| (from,trees) <- parse' pgf input mcat mfrom,
|
("linearizations",showJSON
|
||||||
tree <- trees]
|
[toJSObject [("to", showJSON to),
|
||||||
|
("text",showJSON output)]
|
||||||
|
| (to,output) <- linearizeAndBind pgf mto tree]
|
||||||
|
)]
|
||||||
|
| tree <- trees])]
|
||||||
|
jsonParseOutput (PGF.ParseFailed _) = []
|
||||||
|
jsonParseOutput (PGF.TypeError errs) = [("typeErrors",showJSON [show (PGF.ppTcError err) | (fid,err) <- errs])]
|
||||||
|
|
||||||
-- used in phrasebook
|
-- used in phrasebook
|
||||||
doTranslateGroup :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> Maybe PGF.Language -> JSValue
|
doTranslateGroup :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> Maybe PGF.Language -> JSValue
|
||||||
@@ -137,8 +141,8 @@ doTranslateGroup pgf input mcat mfrom mto =
|
|||||||
(ts,alt) <- output, let lg = length output])
|
(ts,alt) <- output, let lg = length output])
|
||||||
]
|
]
|
||||||
|
|
|
|
||||||
(from,trees) <- parse' pgf input mcat mfrom,
|
(from,po,bs) <- parse' pgf input mcat mfrom,
|
||||||
(to,output) <- groupResults [(t, linearize' pgf mto t) | t <- trees]
|
(to,output) <- groupResults [(t, linearize' pgf mto t) | t <- case po of {PGF.ParseOk ts -> ts; _ -> []}]
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
groupResults = Map.toList . foldr more Map.empty . start . collect
|
groupResults = Map.toList . foldr more Map.empty . start . collect
|
||||||
@@ -187,9 +191,14 @@ doTranslateGroup pgf input mcat mfrom mto =
|
|||||||
|
|
||||||
doParse :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> JSValue
|
doParse :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> JSValue
|
||||||
doParse pgf input mcat mfrom = showJSON $ map toJSObject
|
doParse pgf input mcat mfrom = showJSON $ map toJSObject
|
||||||
[[("from", PGF.showLanguage from),("tree",PGF.showExpr [] tree)]
|
[("from", showJSON from) :
|
||||||
| (from,trees) <- parse' pgf input mcat mfrom,
|
("brackets", showJSON bs) :
|
||||||
tree <- trees ]
|
jsonParseOutput po
|
||||||
|
| (from,po,bs) <- parse' pgf input mcat mfrom]
|
||||||
|
where
|
||||||
|
jsonParseOutput (PGF.ParseOk trees) = [("trees",showJSON trees)]
|
||||||
|
jsonParseOutput (PGF.ParseFailed _) = []
|
||||||
|
jsonParseOutput (PGF.TypeError errs) = [("typeErrors",showJSON [show (PGF.ppTcError err) | (fid,err) <- errs])]
|
||||||
|
|
||||||
doComplete :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> Maybe Int -> JSValue
|
doComplete :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> Maybe Int -> JSValue
|
||||||
doComplete pgf input mcat mfrom mlimit = showJSON $ map toJSObject $ limit
|
doComplete pgf input mcat mfrom mlimit = showJSON $ map toJSObject $ limit
|
||||||
@@ -210,7 +219,7 @@ doRandom pgf mcat mlimit =
|
|||||||
where limit = take (fromMaybe 1 mlimit)
|
where limit = take (fromMaybe 1 mlimit)
|
||||||
|
|
||||||
doGrammar :: PGF -> Maybe (Accept Language) -> JSValue
|
doGrammar :: PGF -> Maybe (Accept Language) -> JSValue
|
||||||
doGrammar pgf macc = showJSON $ toJSObject
|
doGrammar pgf macc = showJSON $ toJSObject
|
||||||
[("name", showJSON (PGF.abstractName pgf)),
|
[("name", showJSON (PGF.abstractName pgf)),
|
||||||
("userLanguage", showJSON (selectLanguage pgf macc)),
|
("userLanguage", showJSON (selectLanguage pgf macc)),
|
||||||
("categories", showJSON categories),
|
("categories", showJSON categories),
|
||||||
@@ -218,8 +227,7 @@ doGrammar pgf macc = showJSON $ toJSObject
|
|||||||
("languages", showJSON languages)]
|
("languages", showJSON languages)]
|
||||||
where languages = map toJSObject
|
where languages = map toJSObject
|
||||||
[[("name", showJSON l),
|
[[("name", showJSON l),
|
||||||
("languageCode", showJSON $ fromMaybe "" (PGF.languageCode pgf l)),
|
("languageCode", showJSON $ fromMaybe "" (PGF.languageCode pgf l))]
|
||||||
("canParse", showJSON $ canParse pgf l)]
|
|
||||||
| l <- PGF.languages pgf]
|
| l <- PGF.languages pgf]
|
||||||
categories = [PGF.showCId cat | cat <- PGF.categories pgf]
|
categories = [PGF.showCId cat | cat <- PGF.categories pgf]
|
||||||
functions = [PGF.showCId fun | fun <- PGF.functions pgf]
|
functions = [PGF.showCId fun | fun <- PGF.functions pgf]
|
||||||
@@ -318,20 +326,24 @@ instance JSON PGF.Expr where
|
|||||||
readJSON x = readJSON x >>= maybe (fail "Bad expression.") return . PGF.readExpr
|
readJSON x = readJSON x >>= maybe (fail "Bad expression.") return . PGF.readExpr
|
||||||
showJSON = showJSON . PGF.showExpr []
|
showJSON = showJSON . PGF.showExpr []
|
||||||
|
|
||||||
|
instance JSON PGF.BracketedString where
|
||||||
|
readJSON x = return (PGF.Leaf "")
|
||||||
|
showJSON x = showJSON ""
|
||||||
|
|
||||||
-- * PGF utilities
|
-- * PGF utilities
|
||||||
|
|
||||||
cat :: PGF -> Maybe PGF.Type -> PGF.Type
|
cat :: PGF -> Maybe PGF.Type -> PGF.Type
|
||||||
cat pgf mcat = fromMaybe (PGF.startCat pgf) mcat
|
cat pgf mcat = fromMaybe (PGF.startCat pgf) mcat
|
||||||
|
|
||||||
parse' :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> [(PGF.Language,[PGF.Tree])]
|
parse' :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> [(PGF.Language,PGF.ParseOutput,PGF.BracketedString)]
|
||||||
parse' pgf input mcat mfrom =
|
parse' pgf input mcat mfrom =
|
||||||
[(from,ts) | from <- froms, canParse pgf from, (PGF.ParseOk ts,_) <- [PGF.parse_ pgf from cat input]]
|
[(from,po,bs) | from <- froms, (po,bs) <- [PGF.parse_ pgf from cat input]]
|
||||||
where froms = maybe (PGF.languages pgf) (:[]) mfrom
|
where froms = maybe (PGF.languages pgf) (:[]) mfrom
|
||||||
cat = fromMaybe (PGF.startCat pgf) mcat
|
cat = fromMaybe (PGF.startCat pgf) mcat
|
||||||
|
|
||||||
complete' :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> [(PGF.Language,[String])]
|
complete' :: PGF -> String -> Maybe PGF.Type -> Maybe PGF.Language -> [(PGF.Language,[String])]
|
||||||
complete' pgf input mcat mfrom =
|
complete' pgf input mcat mfrom =
|
||||||
[(from,order ss) | from <- froms, canParse pgf from, let ss = PGF.complete pgf from cat input, not (null ss)]
|
[(from,order ss) | from <- froms, let ss = PGF.complete pgf from cat input, not (null ss)]
|
||||||
where froms = maybe (PGF.languages pgf) (:[]) mfrom
|
where froms = maybe (PGF.languages pgf) (:[]) mfrom
|
||||||
cat = fromMaybe (PGF.startCat pgf) mcat
|
cat = fromMaybe (PGF.startCat pgf) mcat
|
||||||
order = sortBy (compare `on` map toLower)
|
order = sortBy (compare `on` map toLower)
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
APPDIR=`dirname $0`;
|
APPDIR=`dirname $0`;
|
||||||
|
|
||||||
|
GWT_DIR="/home/angelov/gwt-linux-1.5.3"
|
||||||
|
GWT_CLASSPATH="$GWT_DIR/gwt-user.jar:$GWT_DIR/gwt-dev-linux.jar"
|
||||||
|
|
||||||
if [ -z "$GWT_CLASSPATH" ]; then
|
if [ -z "$GWT_CLASSPATH" ]; then
|
||||||
echo 'ERROR: $GWT_CLASSPATH is not set'
|
echo 'ERROR: $GWT_CLASSPATH is not set'
|
||||||
echo 'Set $GWT_CLASSPATH to point to the GWT JAR files. For example:'
|
echo 'Set $GWT_CLASSPATH to point to the GWT JAR files. For example:'
|
||||||
|
|||||||
@@ -5,18 +5,8 @@ import java.util.List;
|
|||||||
import com.allen_sauer.gwt.dnd.client.PickupDragController;
|
import com.allen_sauer.gwt.dnd.client.PickupDragController;
|
||||||
import com.allen_sauer.gwt.dnd.client.drop.DropController;
|
import com.allen_sauer.gwt.dnd.client.drop.DropController;
|
||||||
import com.google.gwt.core.client.EntryPoint;
|
import com.google.gwt.core.client.EntryPoint;
|
||||||
import com.google.gwt.user.client.History;
|
import com.google.gwt.user.client.*;
|
||||||
import com.google.gwt.user.client.HistoryListener;
|
import com.google.gwt.user.client.ui.*;
|
||||||
import com.google.gwt.user.client.Window;
|
|
||||||
import com.google.gwt.user.client.WindowResizeListener;
|
|
||||||
import com.google.gwt.user.client.ui.ChangeListener;
|
|
||||||
import com.google.gwt.user.client.ui.ClickListener;
|
|
||||||
import com.google.gwt.user.client.ui.DockPanel;
|
|
||||||
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
|
|
||||||
import com.google.gwt.user.client.ui.HasVerticalAlignment;
|
|
||||||
import com.google.gwt.user.client.ui.RootPanel;
|
|
||||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
|
||||||
import com.google.gwt.user.client.ui.Widget;
|
|
||||||
|
|
||||||
|
|
||||||
public class FridgeApp implements EntryPoint {
|
public class FridgeApp implements EntryPoint {
|
||||||
@@ -57,10 +47,21 @@ public class FridgeApp implements EntryPoint {
|
|||||||
new PGF.TranslateCallback() {
|
new PGF.TranslateCallback() {
|
||||||
public void onResult (PGF.Translations translations) {
|
public void onResult (PGF.Translations translations) {
|
||||||
outputPanel.removeStyleDependentName("working");
|
outputPanel.removeStyleDependentName("working");
|
||||||
for (PGF.Translation t : translations.iterable()) {
|
for (PGF.TranslationResult tr : translations.iterable()) {
|
||||||
for (PGF.Linearization l : t.getLinearizations().iterable()) {
|
if (tr.getTranslations() != null)
|
||||||
outputPanel.add(createTranslation(l.getTo(), l.getText()));
|
for (PGF.Translation t : tr.getTranslations().iterable()) {
|
||||||
}
|
for (PGF.Linearization l : t.getLinearizations().iterable()) {
|
||||||
|
outputPanel.add(createTranslation(l.getTo(), l.getText()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.getTypeErrors() != null)
|
||||||
|
for (String error : tr.getTypeErrors()) {
|
||||||
|
SimplePanel panel = new SimplePanel();
|
||||||
|
panel.addStyleName("my-typeError");
|
||||||
|
panel.add(new HTML("<pre>"+error+"</pre>"));
|
||||||
|
outputPanel.add(panel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void onError (Throwable e) {
|
public void onError (Throwable e) {
|
||||||
|
|||||||
@@ -55,14 +55,22 @@ public class PGF {
|
|||||||
|
|
||||||
public interface TranslateCallback extends JSONCallback<Translations> { }
|
public interface TranslateCallback extends JSONCallback<Translations> { }
|
||||||
|
|
||||||
public static class Translations extends IterableJsArray<Translation> {
|
public static class Translations extends IterableJsArray<TranslationResult> {
|
||||||
protected Translations() { }
|
protected Translations() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class TranslationResult extends JavaScriptObject {
|
||||||
|
protected TranslationResult() { }
|
||||||
|
|
||||||
|
public final native String getFrom() /*-{ return this.from; }-*/;
|
||||||
|
public final native String getBracketedString() /*-{ return this.brackets; }-*/;
|
||||||
|
public final native IterableJsArray<Translation> getTranslations() /*-{ return this.translations; }-*/;
|
||||||
|
public final native String[] getTypeErrors() /*-{ return this.typeErrors; }-*/;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Translation extends JavaScriptObject {
|
public static class Translation extends JavaScriptObject {
|
||||||
protected Translation() { }
|
protected Translation() { }
|
||||||
|
|
||||||
public final native String getFrom() /*-{ return this.from; }-*/;
|
|
||||||
public final native String getTree() /*-{ return this.tree; }-*/;
|
public final native String getTree() /*-{ return this.tree; }-*/;
|
||||||
public final native Linearizations getLinearizations() /*-{ return this.linearizations; }-*/;
|
public final native Linearizations getLinearizations() /*-{ return this.linearizations; }-*/;
|
||||||
}
|
}
|
||||||
@@ -129,7 +137,9 @@ public class PGF {
|
|||||||
protected ParseResult() { }
|
protected ParseResult() { }
|
||||||
|
|
||||||
public final native String getFrom() /*-{ return this.from; }-*/;
|
public final native String getFrom() /*-{ return this.from; }-*/;
|
||||||
public final native String getTree() /*-{ return this.tree; }-*/;
|
public final native String getBracketedString() /*-{ return this.brackets; }-*/;
|
||||||
|
public final native String[] getTrees() /*-{ return this.trees; }-*/;
|
||||||
|
public final native String[] getTypeErrors() /*-{ return this.typeErrors; }-*/;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String graphvizAbstractTree(String pgfURL, String abstractTree) {
|
public String graphvizAbstractTree(String pgfURL, String abstractTree) {
|
||||||
|
|||||||
@@ -45,25 +45,36 @@ public class TranslateApp implements EntryPoint {
|
|||||||
public void onResult (PGF.Translations translations) {
|
public void onResult (PGF.Translations translations) {
|
||||||
outputPanel.clear();
|
outputPanel.clear();
|
||||||
outputPanel.removeStyleDependentName("working");
|
outputPanel.removeStyleDependentName("working");
|
||||||
for (PGF.Translation t : translations.iterable()) {
|
for (PGF.TranslationResult tr : translations.iterable()) {
|
||||||
HorizontalPanel hPanel = new HorizontalPanel();
|
if (tr.getTranslations() != null)
|
||||||
hPanel.addStyleName("my-translation-frame");
|
for (PGF.Translation t : tr.getTranslations().iterable()) {
|
||||||
VerticalPanel linsPanel = new VerticalPanel();
|
HorizontalPanel hPanel = new HorizontalPanel();
|
||||||
linsPanel.addStyleName("my-translation-bar");
|
hPanel.addStyleName("my-translation-frame");
|
||||||
hPanel.add(linsPanel);
|
VerticalPanel linsPanel = new VerticalPanel();
|
||||||
HorizontalPanel btnPanel = new HorizontalPanel();
|
linsPanel.addStyleName("my-translation-bar");
|
||||||
btnPanel.addStyleName("my-translation-btns");
|
hPanel.add(linsPanel);
|
||||||
btnPanel.setSpacing(4);
|
HorizontalPanel btnPanel = new HorizontalPanel();
|
||||||
btnPanel.add(createAbsTreeButton(t.getTree()));
|
btnPanel.addStyleName("my-translation-btns");
|
||||||
btnPanel.add(createAlignButton(t.getTree()));
|
btnPanel.setSpacing(4);
|
||||||
hPanel.add(btnPanel);
|
btnPanel.add(createAbsTreeButton(t.getTree()));
|
||||||
hPanel.setCellHorizontalAlignment(btnPanel,
|
btnPanel.add(createAlignButton(t.getTree()));
|
||||||
HasHorizontalAlignment.ALIGN_RIGHT);
|
hPanel.add(btnPanel);
|
||||||
outputPanel.add(hPanel);
|
hPanel.setCellHorizontalAlignment(btnPanel,
|
||||||
|
HasHorizontalAlignment.ALIGN_RIGHT);
|
||||||
|
outputPanel.add(hPanel);
|
||||||
|
|
||||||
for (PGF.Linearization l : t.getLinearizations().iterable()) {
|
for (PGF.Linearization l : t.getLinearizations().iterable()) {
|
||||||
linsPanel.add(createTranslation(l.getTo(), t.getTree(), l.getText()));
|
linsPanel.add(createTranslation(l.getTo(), t.getTree(), l.getText()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tr.getTypeErrors() != null)
|
||||||
|
for (String error : tr.getTypeErrors()) {
|
||||||
|
SimplePanel panel = new SimplePanel();
|
||||||
|
panel.addStyleName("my-typeError");
|
||||||
|
panel.add(new HTML("<pre>"+error+"</pre>"));
|
||||||
|
outputPanel.add(panel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void onError (Throwable e) {
|
public void onError (Throwable e) {
|
||||||
|
|||||||
@@ -71,6 +71,13 @@ body {
|
|||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.my-typeError {
|
||||||
|
padding: 6px;
|
||||||
|
font-size: 150%;
|
||||||
|
font-weight: bold;
|
||||||
|
background: #B9BEC0;
|
||||||
|
}
|
||||||
|
|
||||||
.my-SettingsPanel {
|
.my-SettingsPanel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
|
|||||||
@@ -72,6 +72,13 @@
|
|||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.my-typeError {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 150%;
|
||||||
|
font-weight: bold;
|
||||||
|
background: #CDFFDA;
|
||||||
|
}
|
||||||
|
|
||||||
.my-translation-btns {
|
.my-translation-btns {
|
||||||
background: #DDDDDD;
|
background: #DDDDDD;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
|||||||
Reference in New Issue
Block a user