Files
raven-client/ayanova/src/components/wiki-control.vue
2021-02-10 16:16:33 +00:00

995 lines
32 KiB
Vue

<template>
<div class="mt-6">
<div>
<v-btn depressed tile @click="toggleReveal">
Wiki<v-icon v-text="reveal ? '$ayiEyeSlash' : '$ayiEye'" right></v-icon
></v-btn>
</div>
<template v-if="reveal">
<template v-if="!readonly">
<div class="mt-2">
<v-btn
text
:outlined="currentView == view.WIKI_VIEW"
@click="currentView = view.WIKI_VIEW"
>
<v-icon>$ayiEye</v-icon>
</v-btn>
<v-btn
text
:outlined="currentView == view.DESIGN_VIEW"
@click="currentView = view.DESIGN_VIEW"
>
<v-icon>$ayiEdit</v-icon>
</v-btn>
<v-btn
text
:outlined="currentView == view.SPLIT_VIEW"
@click="currentView = view.SPLIT_VIEW"
>
<v-icon>$ayiColumns</v-icon>
</v-btn>
</div>
</template>
<v-sheet
v-if="currentView != this.view.HIDDEN_VIEW"
elevation="2"
class="aywiki pa-2 pa-sm-6 mt-2"
>
<v-row v-resize="onResize">
<!-- BUTTONS -->
<v-col v-if="showDesigner()" :cols="12">
<div>
<v-btn depressed tile @click="editClick('bold')">
<v-icon>$ayiBold</v-icon></v-btn
>
<v-btn depressed tile @click="editClick('italic')">
<v-icon>$ayiItalic</v-icon></v-btn
>
<v-btn depressed tile @click="editClick('strike')">
<v-icon>$ayiStrikethrough</v-icon></v-btn
>
<!-- HEADINGS -->
<v-menu offset-y>
<template v-slot:activator="{ on }">
<v-btn depressed tile v-on="on">
<v-icon>$ayiHeading</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item @click="editClick('heading', 1)">
<v-list-item-title
><h1>{{ $ay.t("Heading") }} 1</h1></v-list-item-title
>
</v-list-item>
<v-list-item @click="editClick('heading', 2)">
<v-list-item-title
><h2>{{ $ay.t("Heading") }} 2</h2></v-list-item-title
>
</v-list-item>
<v-list-item @click="editClick('heading', 3)">
<v-list-item-title
><h3>{{ $ay.t("Heading") }} 3</h3></v-list-item-title
>
</v-list-item>
<v-list-item @click="editClick('heading', 4)">
<v-list-item-title
><h4>{{ $ay.t("Heading") }} 4</h4></v-list-item-title
>
</v-list-item>
<v-list-item @click="editClick('heading', 5)">
<v-list-item-title
><h5>{{ $ay.t("Heading") }} 5</h5></v-list-item-title
>
</v-list-item>
<v-list-item @click="editClick('heading', 6)">
<v-list-item-title
><h6>{{ $ay.t("Heading") }} 6</h6></v-list-item-title
>
</v-list-item>
</v-list>
</v-menu>
<!-- /HEADINGS -->
<!-- MORE BUTTONS -->
<v-btn depressed tile class="ml-1" @click="editClick('line')">
<v-icon>$ayiMinus</v-icon></v-btn
>
<v-btn depressed tile @click="editClick('quote')">
<v-icon>$ayiQuoteLeft</v-icon></v-btn
>
<v-btn depressed tile @click="editClick('ul')">
<v-icon>$ayiListUl</v-icon></v-btn
>
<v-btn depressed tile @click="editClick('ol')">
<v-icon>$ayiListOl</v-icon></v-btn
>
<v-btn depressed tile @click="editClick('task')">
<v-icon>$ayiCheckSquare</v-icon></v-btn
>
<!-- TABLES -->
<v-menu
v-model="tableMenu"
:close-on-content-click="false"
offset-y
>
<template v-slot:activator="{ on }">
<v-btn depressed tile class="ml-1" v-on="on">
<v-icon>$ayiTable</v-icon>
</v-btn>
</template>
<v-card width="300">
<v-card-title>{{ $ay.t("Table") }}</v-card-title>
<div class="ma-8">
<v-slider
thumb-size="24"
thumb-label="always"
v-model="tableMenuColumns"
min="1"
max="10"
prepend-icon="$ayiArrowsAltH"
></v-slider>
<v-slider
prepend-icon="$ayiArrowsAltV"
class="mt-8"
thumb-size="24"
thumb-label="always"
v-model="tableMenuRows"
min="1"
max="15"
></v-slider>
</div>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="tableMenu = false">{{
$ay.t("Cancel")
}}</v-btn>
<v-btn color="primary" text @click="editClick('table')">{{
$ay.t("OK")
}}</v-btn>
</v-card-actions>
</v-card>
</v-menu>
<!-- /TABLES -->
<!-- LINK -->
<v-btn depressed tile @click="openLinkMenu">
<v-icon>$ayiLink</v-icon>
</v-btn>
<v-menu
min-width="300"
v-model="linkMenu"
:close-on-content-click="false"
offset-y
:position-x="menuX"
:position-y="menuY"
absolute
>
<v-card width="300">
<v-card-title>{{ $ay.t("InsertLink") }}</v-card-title>
<div class="ma-8">
<v-text-field
v-model="linkUrl"
:label="$ay.t('LinkUrl')"
></v-text-field>
<v-text-field
v-model="linkText"
:label="$ay.t('LinkText')"
></v-text-field>
</div>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="linkMenu = false">{{
$ay.t("Cancel")
}}</v-btn>
<v-btn color="primary" text @click="editClick('link')">{{
$ay.t("OK")
}}</v-btn>
</v-card-actions>
</v-card>
</v-menu>
<!-- /LINKS -->
<!-- IMAGE -->
<v-btn depressed tile @click="openImageMenu">
<v-icon>$ayiImage</v-icon>
</v-btn>
<v-menu
min-width="360"
v-model="imageMenu"
:close-on-content-click="false"
offset-y
:position-x="menuX"
:position-y="menuY"
absolute
>
<v-card>
<v-card-title>{{ $ay.t("InsertImage") }}</v-card-title>
<div>
<v-tabs v-model="imageTab" grow color="primary">
<v-tab key="url">URL</v-tab>
<v-tab key="file">{{ $ay.t("Attachments") }}</v-tab>
<v-tabs-items v-model="imageTab">
<v-tab-item key="url"
><div class="ma-6">
<v-text-field
v-model="imageUrl"
:label="$ay.t('ImageUrl')"
></v-text-field>
<v-text-field
v-model="imageText"
:label="$ay.t('ImageDescription')"
></v-text-field></div
></v-tab-item>
<v-tab-item key="file"
><div class="ma-6">
<v-select
@click="getAttachments"
:label="$ay.t('Attachments')"
v-model="selectedImageAttachment"
:items="attachments"
item-text="name"
item-value="id"
return-object
></v-select>
<v-text-field
v-model="imageText"
:label="$ay.t('ImageDescription')"
></v-text-field></div
></v-tab-item>
</v-tabs-items>
</v-tabs>
</div>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="imageMenu = false">{{
$ay.t("Cancel")
}}</v-btn>
<v-btn
color="primary"
text
@click="editClick('image', imageTab)"
>{{ $ay.t("OK") }}</v-btn
>
</v-card-actions>
</v-card>
</v-menu>
<!-- /IMAGE -->
<!-- CODE -->
<v-btn depressed tile class="ml-1" @click="editClick('code')">
<v-icon>$ayiCode</v-icon></v-btn
>
<v-btn depressed tile @click="editClick('codeblock')">
<v-icon>$ayiSquareFull</v-icon></v-btn
>
<!-- HELP -->
<v-btn depressed tile class="ml-1" @click="goHelp()">
<v-icon>$ayiQuestionCircle</v-icon></v-btn
>
<!-- END OF BUTTONS -->
</div>
</v-col>
<!-- DESIGNER -->
<v-col
v-if="showDesigner()"
:cols="currentView == view.SPLIT_VIEW ? 6 : 12"
>
<div :style="editStyle()">
<v-textarea
v-cloak
@drop.prevent="onDrop"
@dragover.prevent
solo
no-resize
:height="editAreaHeight"
ref="editArea"
@input="handleInput"
@dblclick="handleDoubleClick"
v-model="localVal"
></v-textarea>
</div>
</v-col>
<!-- WIKI auto-grow-->
<v-col
v-if="showWiki()"
:cols="currentView == view.SPLIT_VIEW ? 6 : 12"
>
<div
:style="wikiStyle()"
class="markdown-body"
v-html="compiledOutput()"
></div>
</v-col>
</v-row>
<v-btn depressed tile @click="toggleReveal">
Wiki<v-icon
v-text="reveal ? '$ayiEyeSlash' : '$ayiEye'"
right
></v-icon
></v-btn>
</v-sheet>
</template>
</div>
</template>
<script>
import marked from "marked";
import DOMPurify from "dompurify";
export default {
created() {
// Add a hook to make all links open a new window
//https://github.com/cure53/DOMPurify/blob/master/demos/hooks-target-blank-demo.html
DOMPurify.addHook("afterSanitizeAttributes", function(node) {
// set all elements owning target to target=_blank
if ("target" in node) {
node.setAttribute("target", "_blank");
// prevent https://www.owasp.org/index.php/Reverse_Tabnabbing
node.setAttribute("rel", "noopener noreferrer");
}
// set non-HTML/MathML links to xlink:show=new
if (
!node.hasAttribute("target") &&
(node.hasAttribute("xlink:href") || node.hasAttribute("href"))
) {
node.setAttribute("xlink:show", "new");
}
});
},
beforeDestroy() {
//cleanup
DOMPurify.removeAllHooks();
},
data() {
return {
localVal: this.value,
currentView: 0,
reveal: false,
selection: {
start: 0,
end: 0,
startOfBlock: 0, //block meaning first character after last linefeed before or at start of selection
endOfBlock: 0, //end of block meaning selection expanded to end of line (unless there isn't one)
hasSelection: false
},
editAreaHeight: 300,
view: {
HIDDEN_VIEW: 0,
SPLIT_VIEW: 1,
WIKI_VIEW: 2,
DESIGN_VIEW: 3
},
tableMenu: false,
tableMenuColumns: 2,
tableMenuRows: 2,
linkMenu: false,
menuX: 10,
menuY: 10,
linkUrl: "",
linkText: "",
imageMenu: false,
imageTab: null,
imageUrl: "",
imageText: "",
attachments: [],
selectedImageAttachment: null,
notes: null, //attachment upload notes
uploadFiles: [] //attachment upload files
};
},
props: {
value: { type: String, default: "" },
ayaType: { type: Number, default: null },
ayaId: { type: Number, default: null },
readonly: Boolean
},
watch: {
value(value) {
this.localVal = value ?? "";
}
},
methods: {
goHelp() {
window.open(this.$store.state.helpUrl + "ay-start-form-wiki", "_blank");
},
compiledOutput() {
if (!this.localVal) {
return "";
}
//replace attachment urls with tokenized local urls
let src = this.localVal.replace(/\[ATTACH:(.*)\]/g, function(match, p1) {
return window.$gz.api.attachmentDownloadUrl(p1);
});
return DOMPurify.sanitize(marked(src, { breaks: true }));
},
onResize() {
// this.editAreaHeight = window.innerHeight / 2;
this.editAreaHeight = window.innerHeight * 0.8;
},
toggleReveal() {
this.reveal = !this.reveal;
if (this.reveal) {
this.currentView = this.view.WIKI_VIEW;
}
},
editStyle() {
if (this.currentView == this.view.SPLIT_VIEW) {
return "height: " + this.editAreaHeight + "px;";
} else {
return false; //false attributes don't get rendered
}
},
wikiStyle() {
if (this.currentView == this.view.SPLIT_VIEW) {
return "height: " + this.editAreaHeight + "px;overflow-y:auto;";
} else {
return false; //false attributes don't get rendered
}
},
getSelectedRange(forceBlock) {
let bodyTextArea = this.$refs.editArea.$el.querySelector("textarea");
this.selection.start = bodyTextArea.selectionStart;
this.selection.end = bodyTextArea.selectionEnd;
//some edits only work on a block so if user is just clicked on a line then add make a selection
if (forceBlock) {
//add a character to selection forward if possible but if not then go backward one
if (this.selection.end < this.localVal.length) {
this.selection.end++;
} else {
if (this.selection.start > 0) {
this.selection.start--;
}
}
}
this.selection.hasSelection = this.selection.start != this.selection.end;
//block selection
//start of block...
//default
this.selection.startOfBlock = this.selection.start;
if (this.selection.start > 0) {
//find linefeed prior to current start
let indexOfLineFeed = this.localVal.lastIndexOf(
"\n",
this.selection.start
);
if (indexOfLineFeed != -1) {
this.selection.startOfBlock = indexOfLineFeed + 1;
} else {
//this may be wrong but if it's the first line then I think the block should be the first character
this.selection.startOfBlock = 0;
}
}
//end of block
//default
this.selection.endOfBlock = this.selection.end;
if (this.selection.end > 0) {
//find linefeed prior to current start
let indexOfLineFeed = this.localVal.indexOf("\n", this.selection.end);
if (indexOfLineFeed != -1) {
this.selection.endOfBlock = indexOfLineFeed;
}
}
},
setSelectedRange(start, end) {
let bodyTextArea = this.$refs.editArea.$el.querySelector("textarea");
bodyTextArea.setSelectionRange(start, end);
},
getSelectedText() {
let selectedText = "";
if (this.selection.hasSelection) {
selectedText = this.localVal.substring(
this.selection.start,
this.selection.end
);
}
return selectedText;
},
replaceSelectedText(newString) {
this.localVal = window.$gz.util.stringSplice(
this.localVal,
this.selection.start,
this.selection.end - this.selection.start,
newString
);
//emit input event to parent form for dirty tracking
this.handleInput(this.localVal);
},
getSelectedBlock() {
let selectedText = "";
if (this.selection.hasSelection) {
selectedText = this.localVal.substring(
this.selection.startOfBlock,
this.selection.endOfBlock
);
}
return selectedText; //.trim();
},
replaceSelectedBlock(newString) {
this.localVal = window.$gz.util.stringSplice(
this.localVal,
this.selection.startOfBlock,
this.selection.endOfBlock - this.selection.startOfBlock,
newString
);
//emit input event to parent form for dirty tracking
this.handleInput(this.localVal);
},
handleDoubleClick(i) {
//the purpose of this is only to change the selection if it's got an extra space to the right
//because double clicking on a word with another word after it causes the space to be included
this.getSelectedRange();
let temp = this.getSelectedText();
let tempTrimmed = temp.trimEnd();
let diff = temp.length - tempTrimmed.length;
if (diff != 0) {
//there were some spaces so update the selection range
//force selection to be shorter by diff
this.setSelectedRange(this.selection.start, this.selection.end - diff);
}
},
handleInput(val) {
this.$emit("input", val);
this.localVal = val;
},
switchViewIcon() {
//return the icon that indicates what it will change to if you click it
//mirror of switchview below
if (this.readonly) {
if (this.currentView == this.view.HIDDEN_VIEW) {
return "$ayiEye";
} else {
return "$ayiEyeSlash";
}
return;
}
switch (this.currentView) {
case this.view.HIDDEN_VIEW:
return "$ayiEye";
case this.view.WIKI_VIEW:
return "$ayiColumns";
case this.view.SPLIT_VIEW:
return "$ayiFeather";
case this.view.DESIGN_VIEW:
return "$ayiEye";
}
},
showWiki() {
return (
this.currentView == this.view.WIKI_VIEW ||
this.currentView == this.view.SPLIT_VIEW
);
},
showDesigner() {
return (
this.currentView == this.view.DESIGN_VIEW ||
this.currentView == this.view.SPLIT_VIEW
);
},
switchView() {
//if user can't edit then cycle between hidden and wiki view
if (this.readonly) {
if (this.currentView == this.view.HIDDEN_VIEW) {
this.currentView = this.view.WIKI_VIEW;
} else {
this.currentView = this.view.HIDDEN_VIEW;
}
return;
}
//user can edit so switch OUT of hidden view but never into it
//and cycle between design, split and wiki views only
//never goes into hidden
switch (this.currentView) {
case this.view.HIDDEN_VIEW:
this.currentView = this.view.WIKI_VIEW;
break;
case this.view.WIKI_VIEW:
this.currentView = this.view.SPLIT_VIEW;
break;
case this.view.SPLIT_VIEW:
this.currentView = this.view.DESIGN_VIEW;
break;
case this.view.DESIGN_VIEW:
this.currentView = this.view.WIKI_VIEW;
break;
}
},
visibleIcon() {
return this.wikiVisible ? "$ayiEyeSlash" : "$ayiEye";
},
//EDITING
editClick(editType, ex) {
if (editType != "heading") {
this.getSelectedRange();
}
switch (editType) {
case "bold":
this.replaceSelectedText("**" + this.getSelectedText() + "**");
break;
case "italic":
this.replaceSelectedText("*" + this.getSelectedText() + "*");
break;
case "strike":
this.replaceSelectedText("~~" + this.getSelectedText() + "~~");
break;
case "line":
this.replaceSelectedText("***");
break;
case "heading":
{
this.getSelectedRange(true); //special forces
let prepend = "#".repeat(ex) + " ";
let s = this.getSelectedBlock();
s = s.replace(/\n/gi, "\n" + prepend);
if (s.length > 0 && s[0] != "\n") {
s = prepend + s;
}
this.replaceSelectedBlock(s);
}
break;
case "code":
this.replaceSelectedText("`" + this.getSelectedText() + "`");
break;
case "codeblock":
this.replaceSelectedBlock(
"\n```\n" + this.getSelectedBlock() + "\n```\n"
);
break;
case "quote":
if (this.selection.hasSelection) {
this.replaceSelectedBlock("\n>" + this.getSelectedBlock() + "\n");
} else {
this.replaceSelectedText("\n>");
}
break;
case "ul":
{
if (this.selection.hasSelection) {
let s = this.getSelectedBlock();
s = s.replace(/\n/gi, "\n* ");
if (s.length > 0 && s[0] != "\n") {
s = "* " + s;
}
s = s + "\n";
this.replaceSelectedBlock(s);
} else {
this.replaceSelectedText("\n* ");
}
}
break;
case "ol":
{
if (this.selection.hasSelection) {
let s = this.getSelectedBlock();
let ret = "\n1. ";
let listItem = 1;
for (let i = 0; i < s.length; i++) {
if (s[i] == "\n") {
++listItem;
ret += "\n" + listItem + ". ";
} else {
ret += s[i];
}
}
ret += "\n\n";
this.replaceSelectedBlock(ret);
} else {
this.replaceSelectedText("\n1. ");
}
}
break;
case "task":
{
if (this.selection.hasSelection) {
let s = this.getSelectedBlock();
s = s.replace(/\n/gi, "\n* [ ] ");
if (s.length > 0 && s[0] != "\n") {
s = "* [ ] " + s;
}
s = s + "\n";
this.replaceSelectedBlock(s);
} else {
this.replaceSelectedText("\n* [ ] ");
}
}
break;
case "table":
{
this.tableMenu = false;
//| Column 1 | Column 2 | Column 3 |
//| -------- | -------- | -------- |
//| John | Doe | Male |
//| Mary | Smith | Female |
this.getSelectedRange();
let t = "\n";
//Header
//| CCC | CCC | CCC |
for (let c = 0; c < this.tableMenuColumns; c++) {
t += "| CCC ";
}
t += "|\n";
//Divider
//| --- | --- | --- |
for (let c = 0; c < this.tableMenuColumns; c++) {
t += "| --- ";
}
t += "|\n";
//Rows
//| XXX | XXX | XXX |
for (let r = 0; r < this.tableMenuRows; r++) {
for (let c = 0; c < this.tableMenuColumns; c++) {
t += "| XXX ";
}
t += "|\n";
}
this.replaceSelectedText(t);
}
break;
case "link":
{
this.linkMenu = false;
let url = this.linkUrl;
if (!url) {
return;
}
//force it to a full url so it doesn't attempt to open it as a SPA window
if (!url.includes(":")) {
url = "https://" + url;
}
let t = "[" + this.linkText + "](" + url + ")";
// [MY Awesome LINK](www.ayanova.com)
this.replaceSelectedText(t);
}
break;
case "image":
//![alttexthere](https://www.ayanova.com/images/AyaNovaIcon256.png "title text here (tooltip)")
{
this.imageMenu = false;
let url = null;
if (ex == 1) {
if (
!this.selectedImageAttachment ||
!this.selectedImageAttachment.url
) {
return;
}
//it's an attachment
url = this.selectedImageAttachment.url;
if (!this.imageText) {
this.imageText = this.selectedImageAttachment.name;
}
} else {
//it's a url paste / manual entry
url = this.imageUrl;
}
if (!url) {
return;
}
let txt = this.imageText;
//force it to a full url so it doesn't attempt to open it as a SPA window
if (!url.includes(":")) {
url = "https://" + url;
}
if (txt) {
this.replaceSelectedText(
"![" + txt + "](" + url + ' "' + txt + '") \n' + txt //keep original selected text otherwise it will vanish
);
} else {
this.replaceSelectedText("![](" + url + ")");
}
this.selectedImageAttachment = null;
}
break;
default:
throw new Error(`wiki-control: ${editType} NOT IMPLEMENTED`);
break;
}
// //emit input event to parent form for dirty tracking
// this.handleInput(this.localVal);
},
openLinkMenu(e) {
e.preventDefault();
this.linkMenu = false;
this.getSelectedRange();
this.linkText = this.getSelectedText();
this.menuX = e.clientX;
this.menuY = e.clientY;
this.$nextTick(() => {
this.linkMenu = true;
});
},
openImageMenu(e) {
e.preventDefault();
this.imageMenu = false;
this.getSelectedRange();
this.imageText = this.getSelectedText();
this.menuX = e.clientX;
this.menuY = e.clientY;
this.$nextTick(() => {
this.imageMenu = true;
});
},
async getAttachments() {
let vm = this;
try {
vm.attachments = [];
let res = await window.$gz.api.get(
"attachment/list?ayatype=" + vm.ayaType + "&ayaid=" + vm.ayaId
);
if (res.error) {
window.$gz.errorHandler.handleFormError(res.error);
} else {
let ret = [];
for (let i = 0; i < res.data.length; i++) {
let o = res.data[i];
if (
window.$gz.util.isImageAttachment(
o.displayFileName,
o.contentType
)
) {
//attach url
//![]([ATTACH:4])
ret.push({
id: o.id,
url: "[ATTACH:" + o.id + "]",
name: o.displayFileName
});
}
}
vm.attachments = ret;
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error);
}
},
async upload() {
//similar code in attachment-control upload
let vm = this;
let at = {
ayaId: vm.ayaId,
ayaType: vm.ayaType,
files: vm.uploadFiles,
notes: ""
};
try {
let res = await window.$gz.api.uploadAttachment(at);
if (res.error) {
window.$gz.errorHandler.handleFormError(res.error);
} else {
let ret = [];
for (let i = 0; i < res.data.length; i++) {
let o = res.data[i];
//let them attach any file type to the wiki since it supports it anyway
ret.push({
id: o.id,
url: window.$gz.api.attachmentDownloadUrl(o.id, o.contentType),
name: o.displayFileName
});
//}
}
//put into attachments list
vm.attachments = ret;
//NOW iterate upload files list and insert into wiki based on attachments
//insert into wiki
for (let i = 0; i < vm.uploadFiles.length; i++) {
let upFile = vm.uploadFiles[i];
for (let j = 0; j < vm.attachments.length; j++) {
let atFile = vm.attachments[j];
if (upFile.name == atFile.name) {
//found it
this.insertUrl(atFile.url, atFile.name);
break;
}
}
}
//finally, clear the upload files
vm.uploadFiles = [];
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error);
}
},
onDrop(ev) {
//Drop image file
var files = Array.from(ev.dataTransfer.files);
if (files.length > 0) {
//handle file drop
var files = Array.from(ev.dataTransfer.files);
if (files.length > 0) {
this.uploadFiles = files;
this.upload();
}
//if an image then put it directly in viewable, if not an image then make a link and keep as an attach
} else {
//maybe an url?
let url = ev.dataTransfer.getData("text");
this.insertUrl(url);
}
},
insertUrl(url, name) {
if (url) {
let isImageUrl = false;
//attachment?
if (url.includes("attachment/download/")) {
//it's an attachment url so fixup accordingly
//i paramter added by gzapi::attachmentDownloadUrl function
isImageUrl = url.includes("&i=");
let m = url.match(/attachment\/download\/(.*)\?t=/);
if (m.length > 1) {
url = "[ATTACH:" + m[1] + "]";
} else {
url = null;
}
} else {
//External url, sniff out if it's an image
isImageUrl = window.$gz.util.isImageAttachment(url);
}
if (url != null) {
//insert into textarea
let txt = this.getSelectedText();
if (!txt) {
txt = name;
}
if (txt) {
if (isImageUrl) {
this.replaceSelectedText(
"![" + txt + "](" + url + ' "' + txt + '") \n' + txt + "\n" //keep original selected text otherwise it will vanish
);
} else {
//regular url not image
this.replaceSelectedText(
"[" + txt + "](" + url + ' "' + txt + '")\n'
);
}
} else {
//No selected text
if (isImageUrl) {
this.replaceSelectedText("![](" + url + ")\n");
} else {
//regular no text non image url
this.replaceSelectedText("<" + url + ">\n");
}
}
}
//handle accordingly
}
}
//----------end methods----------
}
};
</script>