diff --git a/dev/sass/controls.scss b/dev/sass/controls.scss
index fbe37955..ab066a22 100644
--- a/dev/sass/controls.scss
+++ b/dev/sass/controls.scss
@@ -4,6 +4,22 @@
border-radius: $control-radius;
}
+.copy-button {
+ margin-top: 10px;
+ padding: 8px 12px;
+ background-color: #4CAF50;
+ color: #fff;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.copy-button:hover {
+ background-color: #45a049;
+}
+
+
input, textarea {
@extend %control;
diff --git a/dev/src/views/Expression.js b/dev/src/views/Expression.js
index c9d45717..ad8708b4 100644
--- a/dev/src/views/Expression.js
+++ b/dev/src/views/Expression.js
@@ -31,51 +31,51 @@ import EventDispatcher from "../events/EventDispatcher"
import app from "../app";
export default class Expression extends EventDispatcher {
- constructor (el) {
+ constructor(el) {
super();
this.el = el;
this.delim = "/";
this.lexer = new ExpressionLexer();
-
+
this._initUI(el);
- app.flavor.on("change", ()=> this._onFlavorChange());
+ app.flavor.on("change", () => this._onFlavorChange());
this._onFlavorChange();
}
-
+
set value(expression) {
let regex = Utils.decomposeRegEx(expression || Expression.DEFAULT_EXPRESSION, this.delim);
this.pattern = regex.source;
this.flags = regex.flags;
}
-
+
get value() {
return this.editor.getValue();
}
-
+
set pattern(pattern) {
let index = this.editor.getValue().lastIndexOf(this.delim);
- this.editor.replaceRange(pattern, {line: 0, ch: 1}, {line: 0, ch: index});
+ this.editor.replaceRange(pattern, { line: 0, ch: 1 }, { line: 0, ch: index });
this._deferUpdate();
}
-
+
get pattern() {
return Utils.decomposeRegEx(this.editor.getValue(), this.delim).source;
}
-
+
set flags(flags) {
flags = app.flavor.validateFlagsStr(flags);
let str = this.editor.getValue(), index = str.lastIndexOf(this.delim);
- this.editor.replaceRange(flags, {line: 0, ch: index + 1}, {line: 0, ch: str.length }); // this doesn't work if readOnly is false.
+ this.editor.replaceRange(flags, { line: 0, ch: index + 1 }, { line: 0, ch: str.length }); // this doesn't work if readOnly is false.
}
-
+
get flags() {
return Utils.decomposeRegEx(this.editor.getValue(), this.delim).flags;
}
-
+
get token() {
return this.lexer.token;
}
-
+
showFlags() {
this.flagsList.selected = this.flags.split("");
app.tooltip.toggle.toggleOn("flags", this.flagsEl, this.flagsBtn, true, -2);
@@ -83,13 +83,13 @@ export default class Expression extends EventDispatcher {
toggleFlag(s) {
let flags = this.flags, i = flags.indexOf(s);
- this.flags = i>=0 ? flags.replace(s, "") : flags+s;
+ this.flags = i >= 0 ? flags.replace(s, "") : flags + s;
}
-
+
showFlavors() {
app.tooltip.toggle.toggleOn("flavor", this.flavorEl, this.flavorBtn, true, -2)
}
-
+
insert(str) {
this.editor.replaceSelection(str, "end");
}
@@ -97,8 +97,8 @@ export default class Expression extends EventDispatcher {
selectAll() {
CMUtils.selectAll(this.editor);
}
-
-// private methods:
+
+ // private methods:
_initUI(el) {
this.editorEl = $.query("> .editor", el);
let editor = this.editor = CMUtils.create(this.editorEl, {
@@ -106,31 +106,48 @@ export default class Expression extends EventDispatcher {
maxLength: 2500,
singleLine: true
}, "100%", "100%");
-
- editor.on("mousedown", (cm, evt)=> this._onEditorMouseDown(cm, evt));
- editor.on("change", (cm, evt)=> this._onEditorChange(cm, evt));
- editor.on("keydown", (cm, evt)=> this._onEditorKeyDown(cm, evt));
- // hacky method to disable overwrite mode on expressions to avoid overwriting flags:
- editor.toggleOverwrite = ()=>{};
-
+
+ // Add Copy Button
+ const copyButton = document.createElement("button");
+ copyButton.textContent = "Copy";
+ copyButton.className = "copy-button";
+ copyButton.addEventListener("click", () => {
+ const patternText = this.pattern || "";
+ navigator.clipboard.writeText(patternText).then(() => {
+ alert("Pattern copied!");
+ }).catch(err => {
+ console.error("Failed to copy pattern:", err);
+ });
+ });
+
+ // Append the button to the parent container
+ this.editorEl.parentElement.appendChild(copyButton);
+
+ editor.on("mousedown", (cm, evt) => this._onEditorMouseDown(cm, evt));
+ editor.on("change", (cm, evt) => this._onEditorChange(cm, evt));
+ editor.on("keydown", (cm, evt) => this._onEditorKeyDown(cm, evt));
+
+ editor.toggleOverwrite = () => { }; // Disable overwrite mode for flags
+
this.errorEl = $.query(".icon.alert", this.editorEl);
- this.errorEl.addEventListener("mouseenter", (evt)=>this._onMouseError(evt));
- this.errorEl.addEventListener("mouseleave", (evt)=>this._onMouseError(evt));
-
+ this.errorEl.addEventListener("mouseenter", (evt) => this._onMouseError(evt));
+ this.errorEl.addEventListener("mouseleave", (evt) => this._onMouseError(evt));
+
this.highlighter = new ExpressionHighlighter(editor);
this.hover = new ExpressionHover(editor, this.highlighter);
-
+
this._setInitialExpression();
this._initTooltips(el);
this.value = Expression.DEFAULT_EXPRESSION;
}
-
+
+
_setInitialExpression() {
let editor = this.editor;
editor.setValue("/./g");
-
+
// leading /
- editor.getDoc().markText({line: 0, ch: 0}, {
+ editor.getDoc().markText({ line: 0, ch: 0 }, {
line: 0,
ch: 1
}, {
@@ -139,9 +156,9 @@ export default class Expression extends EventDispatcher {
atomic: true,
inclusiveLeft: true
});
-
+
// trailing /g
- editor.getDoc().markText({line: 0, ch: 2}, {
+ editor.getDoc().markText({ line: 0, ch: 2 }, {
line: 0,
ch: 4
}, {
@@ -152,11 +169,11 @@ export default class Expression extends EventDispatcher {
});
this._deferUpdate();
}
-
+
_deferUpdate() {
- Utils.defer(()=>this._update(), "Expression._update");
+ Utils.defer(() => this._update(), "Expression._update");
}
-
+
_update() {
let expr = this.editor.getValue();
this.lexer.profile = app.flavor.profile;
@@ -166,50 +183,50 @@ export default class Expression extends EventDispatcher {
this.highlighter.draw(token);
this.dispatchEvent("change");
}
-
+
_initTooltips(el) {
const template = $.template` ${"label"}`;
- let flavorData = app.flavor.profiles.map((o)=>({id:o.id, label:o.label+" ("+(o.browser?"Browser":"Server")+")"}));
-
+ let flavorData = app.flavor.profiles.map((o) => ({ id: o.id, label: o.label + " (" + (o.browser ? "Browser" : "Server") + ")" }));
+
this.flavorBtn = $.query("section.expression .button.flavor", el);
this.flavorEl = $.query("#library #tooltip-flavor");
- this.flavorList = new List($.query("ul.list", this.flavorEl), {data:flavorData, template});
- this.flavorList.on("change", ()=>this._onFlavorListChange());
+ this.flavorList = new List($.query("ul.list", this.flavorEl), { data: flavorData, template });
+ this.flavorList.on("change", () => this._onFlavorListChange());
this.flavorBtn.addEventListener("click", (evt) => this.showFlavors());
- $.query(".icon.help", this.flavorEl).addEventListener("click", ()=> app.sidebar.goto("engine"));
-
+ $.query(".icon.help", this.flavorEl).addEventListener("click", () => app.sidebar.goto("engine"));
+
this.flagsBtn = $.query("section.expression .button.flags", el);
this.flagsEl = $.query("#library #tooltip-flags");
- this.flagsList = new List($.query("ul.list", this.flagsEl), {data:[], multi:true, template});
- this.flagsList.on("change", ()=> this._onFlagListChange());
+ this.flagsList = new List($.query("ul.list", this.flagsEl), { data: [], multi: true, template });
+ this.flagsList.on("change", () => this._onFlagListChange());
this.flagsBtn.addEventListener("click", (evt) => this.showFlags());
- $.query(".icon.help", this.flagsEl).addEventListener("click", ()=> app.sidebar.goto("flags"));
+ $.query(".icon.help", this.flagsEl).addEventListener("click", () => app.sidebar.goto("flags"));
}
-// event handlers:
+ // event handlers:
_onFlavorListChange() {
app.tooltip.toggle.hide("flavor");
app.flavor.value = this.flavorList.selected;
- Track.page("flavor/"+this.flavorList.selected);
+ Track.page("flavor/" + this.flavorList.selected);
}
-
+
_onFlagListChange() {
let sel = this.flagsList.selected;
this.flags = sel ? sel.join("") : "";
Track.event("set_flags", "engagement", this.flags);
}
-
+
_onFlavorChange() {
let flavor = app.flavor, profile = flavor.profile;
this.flavorList.selected = profile.id;
$.query("> .label", this.flavorBtn).innerText = profile.label;
-
- let supported = Expression.FLAGS.split("").filter((n)=>!!profile.flags[n]);
+
+ let supported = Expression.FLAGS.split("").filter((n) => !!profile.flags[n]);
let labels = Expression.FLAG_LABELS;
- this.flagsList.data = supported.map((n)=>({id:n, label:labels[n]}));
- this.flags = this.flags.split("").filter((n)=>!!profile.flags[n]).join("");
+ this.flagsList.data = supported.map((n) => ({ id: n, label: labels[n] }));
+ this.flags = this.flags.split("").filter((n) => !!profile.flags[n]).join("");
}
-
+
_onEditorMouseDown(cm, evt) {
// offset by half a character to make accidental clicks less likely:
let index = CMUtils.getCharIndexAt(cm, evt.clientX - cm.defaultCharWidth() * 0.6, evt.clientY);
@@ -217,8 +234,8 @@ export default class Expression extends EventDispatcher {
this.showFlags();
}
}
-
-
+
+
_onEditorChange(cm, evt) {
// catches pasting full expressions in.
// TODO: will need to be updated to work with other delimeters
@@ -230,7 +247,7 @@ export default class Expression extends EventDispatcher {
}
this.value = str;
}
-
+
_onEditorKeyDown(cm, evt) {
// Ctrl or Command + D by default, will delete the expression and the flags field, Re: https://github.com/gskinner/regexr/issues/74
// So we just manually reset to nothing here.
@@ -239,7 +256,7 @@ export default class Expression extends EventDispatcher {
this.pattern = "";
}
}
-
+
_onMouseError(evt) {
let tt = app.tooltip.hover, errs = this.lexer.errors;
if (evt.type === "mouseleave") { return tt.hide("error"); }
@@ -247,9 +264,9 @@ export default class Expression extends EventDispatcher {
let err = errs.length === 1 && errs[0].error;
let str = err ? app.reference.getError(err, errs[0]) : "Problems in the Expression are underlined in red. Roll over them for details.";
let label = err && err.warning ? "WARNING" : "PARSE ERROR";
- tt.showOn("error", ""+label+": "+str, this.errorEl);
+ tt.showOn("error", "" + label + ": " + str, this.errorEl);
}
-
+
}
Expression.DEFAULT_EXPRESSION = "/([A-Z])\\w+/g";