• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
1

Validating Data in ScriptUI

Contributor ,
May 25, 2023 May 25, 2023

Copy link to clipboard

Copied

I was referred to @Peter Kahrel excellent tutorial on ScriptUI located here https://creativepro.com/files/kahrel/indesign/scriptui.html, but I am having a few issues with my scripts:

  • On page 93, he has an example with this code:

 

w.input = w.group.add ('edittext {characters: 10, active: true, justify: "right"}');​

I don't see where "characters: 10" is explained. I thought it would limit the input to 10 characters, but it does not seem to, either in his example or in my testing.  Is there a way to do this? One of my input fields needs to be 7 characters and one of them needs to be 8 character date in format YYYYMMDD.

 

  • For the field that needs to be 7 characters, I use an Apply instead of an Okay button and I used his red-background code (and modified it to NOT allow commas or decimals). But I don't want the apply button enabled unless the field has seven characters - not 8 or 6. I tried this and it doesn't work:

 

            win.Panel2.txt2.onChanging = function () {
                var valid = /^[\d]+$/.test (win.Panel2.txt2.text);
                this.graphics.backgroundColor = this.graphics.newBrush (this.graphics.BrushType.
                SOLID_COLOR, valid ? [1, 1, 1, 1] : [1, 0.5, 0.5, 1]);
                win.Panel2.ApplyBtn.enabled = valid && win.Panel2.txt2.text.length=7;              
                }​

 

  • Similarly for the date field, I found this: https://www.scaler.com/topics/date-validation-in-javascript/ and I verified Date.parse() generates NaN for an invalid date in the format above and a number for a valid date. I want the apply button to be enabled only if I have a valid date in the field, and I want the red text if the field contains anything besides numbers. I tried this and it doesn't work - the Apply button is never enabled:

 

            win.Panel2.txt3.onChanging = function () {
                var valid = /^[\d]+$/.test (win.Panel2.txt3.text);
                this.graphics.backgroundColor = this.graphics.newBrush (this.graphics.BrushType.
                SOLID_COLOR, valid ? [1, 1, 1, 1] : [1, 0.5, 0.5, 1]);
                var valid2 = Date.parse(win.Panel2.txt3.text);
                win.Panel2.ApplyBtn.enabled = !valid2==NaN;              
                }​

 

 

I'll probably have more questions as I get further into my script, and I'll add them to this thread, if that is okay.

 

(The forum gave me an error about invalid HTML that it removed, but I'm not seeing what it changed).

 

Thanks in advance!

TOPICS
Scripting

Views

2.0K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 4 Correct answers

Community Expert , May 26, 2023 May 26, 2023

= is used to assign a value to a variable.

== and === test equality. === is used for strict equality, which means that the two items you compare must be the same type. == is for looser comparisons. == is slower because it tries to coerce the two items into type-equality.

 

Example:

 

var a = 5;

var b = '5';

a == b; // => true

a === b // => false

 

a is a number, b is a string. Strict comparison returns false because the variables are not the same type. Loose comparison returns true, it successfu

...

Votes

Translate

Translate
Guide , Jun 03, 2023 Jun 03, 2023

Playing with text then textselection, in that order, may solve the cursor issue in EditText widgets. That's a bit of black magic so I do not guarantee the universality of the following code:

 

   // . . .
   // Your UI components (w, w.input, etc)
   // . . .

   // Predeclare brushes
   var gx = w.input.graphics;
   var BH =
   [
   gx.newBrush(gx.BrushType.SOLID_COLOR, [1.0, 0.5, 0.5, 1]), // KO
   gx.newBrush(gx.BrushType.SOLID_COLOR, [1.0, 1.0, 1.0, 1])  // OK
   ];

   // Text changing handle
...

Votes

Translate

Translate
Contributor , Jun 14, 2023 Jun 14, 2023

@Marc Autret (or others)

Funny how things work - or maybe not if you are religious!!! Anyway, I was getting really frustrated and typed up a long post. I hit preview and walked away from my computer and never posted it. I couldn't see the forest for the trees, but I solved the problem!!!

 

This will be a long post, but it should help some people.

 

The main problem I was having had to do with "partially valid" regex. I have enough trouble coming up with "fully valid" regex - although I think I have t

...

Votes

Translate

Translate
Guide , Jun 20, 2023 Jun 20, 2023

Hi @Marshall_Brooks 

 

Sorry I don't understand your code 😕 I'm afraid I've already detailed everything I can say about my own approach, so the code below basically just repeats the same logic.

 

Some advice to prevent infinite loops and other errors: Keep your objects encapsulated, only play with text/textselection once (and only if needed!), do not take the risk of reintroducing a changing event while you modify the text, do not resolve again the EditText instance (it is and remains this within t

...

Votes

Translate

Translate
Contributor ,
May 25, 2023 May 25, 2023

Copy link to clipboard

Copied

Figured out the first issue:

win.Panel2.ApplyBtn.enabled = valid && win.Panel2.txt2.text.length=7;

needs to be:

win.Panel2.ApplyBtn.enabled = valid && win.Panel2.txt2.text.length==7;

 Double == before the 7

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 25, 2023 May 25, 2023

Copy link to clipboard

Copied

Ran into a hiccup with the date. Date.parse() will not work for me. 20230523 is valid and what I want, but 05232023 is also valid, but NOT compatible for my purposes. There is code further up the page that should work, but it doesn't seem to work with ScriptUI/ESTK.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 25, 2023 May 25, 2023

Copy link to clipboard

Copied

I figured out the date validation code and posted it in the other thread related to this:

https://community.adobe.com/t5/framemaker-discussions/extendscript-and-date-formats/td-p/13798426/pa...

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
May 25, 2023 May 25, 2023

Copy link to clipboard

Copied

> I don't see where "characters: 10" is explained.

 

Well, the PDF has an index, which has an entry for 'characters' and guides you to p. 11, where it says:

 

> The characters property is used to set the control’s width

 

The control's width is not the same as the number of characters that you can enter in the control, but I'll agree with you that there's room for confusion here.
You can monitor what's entered into a control ('field'), there are some examples of that in the PDF.

 

> But I don't want the apply button enabled unless the field has seven characters

 

Take a look at this script: https://creativepro.com/files/kahrel/indesign/grep_query_manager.html
There's an option to save a configuration as a preset. The interface has two buttons: Save and Cancel. While you're typing a preset name, if what you type matches an existing preset, the Save button's text changes to Replace. It's in the function getValidName

 

> I'll probably have more questions as I get further into my script, and I'll add them to this thread, if that is okay.

 

Absolutely!

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
May 25, 2023 May 25, 2023

Copy link to clipboard

Copied

Here's a sample of how to enable a button only when a text field's content has a certain length:

 

 

w = new Window ('dialog');
  w.field = w.add ('edittext {characters: 10, active: true}');
  w.button = w.add ('button {text: "Apply"}');

  w.field.onChanging = function () {
    w.button.enabled = w.field.text.length === 7;
  }

w.show();

 

 

You'd probably need to set the burron's state when the script is started, but I didn't add that here because the field might be populated from a history file.

 

(But I now see that you already fixed it. Mustard after the meal. Sorry.)

 

P.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 25, 2023 May 25, 2023

Copy link to clipboard

Copied

Actually, stupid question time ...

I've done much more in Visual Basic, thus =7 seemed logical to me. I changed it to ==7 and that worked. You said ===7.

What are the differences and when would I use either one?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
May 26, 2023 May 26, 2023

Copy link to clipboard

Copied

= is used to assign a value to a variable.

== and === test equality. === is used for strict equality, which means that the two items you compare must be the same type. == is for looser comparisons. == is slower because it tries to coerce the two items into type-equality.

 

Example:

 

var a = 5;

var b = '5';

a == b; // => true

a === b // => false

 

a is a number, b is a string. Strict comparison returns false because the variables are not the same type. Loose comparison returns true, it successfully coerces the variables into the same type (string or number, I don't know which).

 

Because strict comparison evaluates considerably quicker you should use it where you can. Especially in InDesign's DOM, when you test some enumeration repeatedly. For example

 

if (myPage.side === PageSideOptions.LEFT_HAND) {

 

is considerably quicker than

 

if (myPage.side == PageSideOptions.LEFT_HAND) {

 

If you don't know whether the two items to be compared are type-compatible, use ==

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 30, 2023 May 30, 2023

Copy link to clipboard

Copied

Excellent explanation - like I said, my main background is Visual Basic, which is only one "=" sign and the equivalent of "==" would be a type mismatch error.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 30, 2023 May 30, 2023

Copy link to clipboard

Copied

Okay - this is what I have so far:

var w = new Window ('dialog');
// First attempt - allows more than 7 Characters.
w.group = w.add ('group');
w.group.add ('statictext {text: "Enter a 7-Digit number:"}');
w.input = w.group.add ('edittext {characters: 10, active: true, justify: "right"}');
w.buttons = w.add ('group {alignment: "right"}');
w.ok = w.buttons.add ('button {text: "OK", enabled: false}');
w.buttons.add ('button {text: "Cancel"}');
w.input.onChanging = function () {
var valid = /^[\d]+$/.test (w.input.text);
this.graphics.backgroundColor = this.graphics.newBrush (this.graphics.BrushType.
SOLID_COLOR, valid ? [1, 1, 1, 1] : [1, 0.5, 0.5, 1]);
w.ok.enabled = valid && w.input===7;              
}
w.show();

It works fairly well - if you put anything other than a digit, it turns red, if you have more or less than 7 characters, OK is not enabled.

What I ideally want:

Field starts with "XXXXXXX". I will add a clear button that empties the field. I want cut and paste to work, but I only want to allow digits in the field (and probably delete or backspace). If the user types "A", I don't want an "A" and red background, I just want the input ignored, and I don't want more than 7 characters, if the field text is already 7 characters, I want the input ignored.

There were some helpful answers here, but https://stackoverflow.com/questions/2808184/restricting-input-to-textbox-allowing-only-numbers-and-d... but the most elegant seemed to be:

var validNumber = new RegExp(/^\d*\.?\d*$/);
var lastValid = document.getElementById("test1").value;
function validateNumber(elem) {
  if (validNumber.test(elem.value)) {
    lastValid = elem.value;
  } else {
    elem.value = lastValid;
  }
}

But I wasn't sure how to implement it - or if I had to be using the keyboard input value or the onchanging value.
Thanks in advance!!!

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 30, 2023 May 30, 2023

Copy link to clipboard

Copied

Something I thought would be easy...

I started with a previous script that I had written which had panel 1 above panel 2. For this script, I would like to have panel1 on the left side of the window and Panel 2 next to it on the right. I would prefer the window to auto-size.

I thought this would work:

    var win = new Window("palette", "My Window:"); // bounds = [left, top, right, bottom]
    win.radioPanel = win.add("panel", [5, 10, 230, 90], "Panel 1");
    win.Panel2 = win.add("panel", [235, 10, 505, 230], "Panel 2");
    win.show();

But it still shows Panel 1 above Panel 2.

This works:

    var win = new Window("palette",'MyWindow:', [15, 45, 525, 300]); // bounds = [left, top, right, bottom]
    win.radioPanel = win.add("panel", [5, 10, 230, 90], "Panel 1");
    win.Panel2 = win.add("panel", [235, 10, 505, 230], "Panel 2");
    win.show();

But if I use this, I think ideally, I need to figure out which screen is running FM, what the resolution of that screen is, set left to 1/2 screen width - 1/2 window width, set top to 1/2 screen heigth - 1/2 window height, etc.

Is there a simpler way?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 30, 2023 May 30, 2023

Copy link to clipboard

Copied

Somewhat clunky, but:

   //var win = new Window("palette",'MyWindow:', [15, 45, 525, 300]); // bounds = [left, top, right, bottom]
   var win = new Window("palette",'MyWindow:', [$.screens[0].right/2 - 253,$.screens[0].bottom/2 - 127, $.screens[0].right/2 + 253, $.screens[0].bottom/2 + 127]); // bounds = [left, top, right, bottom]
   //var win = new Window("palette", "My Window:"); // bounds = [left, top, right, bottom]
    win.radioPanel = win.add("panel", [5, 10, 230, 90], "Panel 1");
    win.Panel2 = win.add("panel", [235, 10, 505, 230], "Panel 2");
    win.show();

Works for single-screen or dual-monitor if FrameMaker is on screen 1. (Otherwise, it will show the dialog on Screen 1.) If I could figure out how to tell which screen has the active document, I could use that in the code and it would work.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 30, 2023 May 30, 2023

Copy link to clipboard

Copied

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Jun 05, 2023 Jun 05, 2023

Copy link to clipboard

Copied

quote

See https://community.adobe.com/t5/framemaker-discussions/determine-screen-number-of-active-document/m-p... - for the sizing - at least the clunky method ...

Does not work. I need to know how to display the window on Screen 2 but only if FM is on Screen 2.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jun 05, 2023 Jun 05, 2023

Copy link to clipboard

Copied

If you want to design and visual see the dialog. I recommend the website made by joonas. He has made a scriptui website in which you can drag and drop components and instantly see the design

https://scriptui.joonas.me/

Its a huge time saver and also returns very clean code.

Ive been using it now for couple years and it has taken out that slow peocess of testing and checking dialog design

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Jun 06, 2023 Jun 06, 2023

Copy link to clipboard

Copied

Thank you! - really cool site!!!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jun 06, 2023 Jun 06, 2023

Copy link to clipboard

Copied

Its si easy and fast now ti make the dialogs. Its godsend tool. Really big props to joonas

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 31, 2023 May 31, 2023

Copy link to clipboard

Copied

I tried to implement the StackOverflow solution. Logically, it makes sense to me. If you type a valid character, it stores the field text in the lastValid variable. If you add a valid character, it increases that variable. If you type an invalid character, it replaces the field with the last valid content - basically it deletes the character you just input.

 

The issue I am having is lastValid becomes undefined, and I'm not sure why.  Here is what I have so far:

// Attempt to limit input.
w.group = w.add ('group');
w.group.add ('statictext {text: "Enter a 7-Digit number Anything other than a numeral will be ignored:"}');
w.input = w.group.add ('edittext {characters: 10, active: true, justify: "right"}');
w.clear = w.group.add ('button {text: "Clear"}');
w.buttons = w.add ('group {alignment: "right"}');
w.ok = w.buttons.add ('button {text: "OK", enabled: false}');
w.buttons.add ('button {text: "Cancel"}');
w.input.text="XXXXXXX"
var lastValid = w.input.text;
alert(lastValid); //  returns XXXXXX
            w.input.onChanging = function (lastValid) {
                alert(lastValid); // returns undefined
                w.ok.enabled = false;
                var valid = /^[\d]+$/.test (w.input.text)&& w.input.text.length===7;
                this.graphics.backgroundColor = this.graphics.newBrush (this.graphics.BrushType.
                SOLID_COLOR, valid ? [1, 1, 1, 1] : [1, 0.5, 0.5, 1]);
                w.ok.enabled = valid;
                alert(lastValid); // returns undefined
                var valid2 = /^[\d]+$/.test (w.input.text)&& w.input.text.length<7;              
                if (valid2){
                    alert("valid2");
                    lastValid = w.input.text;
                    alert(lastValid); // Returns field contents
                    }
                else{
                    alert("Not valid2");
                    w.input.text = lastValid;  // changes field contents to undefined
                }    
            }
            w.clear.onClick = function(){
                var valid = /^[\d]+$/.test (w.input.text) && w.input.text.length===7;
                if (valid){
                    if (confirm("Do you want clear the input field") ===false) {
                        return;
                        }
                    }
                w.input.text = "";            
                }
 w.show();

Initially, I have lastValid defined in the onChanging event function, but then it was updating whether the input was valid or not, so it didn't work.

 

Hopefully, someone sees a typo above, but otherwise, I'm going to look into keypress events, and then give up on this.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
May 31, 2023 May 31, 2023

Copy link to clipboard

Copied

Took a brute force approach.  Again, what I am looking for is anything other than a number is ignored, and once 7 characters are reached, any input is ignored.

New Code:

var w = new Window ('palette');
// Secong Attempt to limit input.
w.group = w.add ('group');
w.group.add ('statictext {text: "Enter a 7-Digit number Anything other than a numeral will be ignored:"}');
w.input = w.group.add ('edittext {characters: 10, active: true, justify: "right"}');
w.clear = w.group.add ('button {text: "Clear"}');
w.buttons = w.add ('group {alignment: "right"}');
w.ok = w.buttons.add ('button {text: "OK", enabled: false}');
w.buttons.add ('button {text: "Cancel"}');
            w.input.onChanging = function (lastValid) {
                w.ok.enabled = false;
                var valid = /^[\d]+$/.test (w.input.text)&& w.input.text.length===7;
                this.graphics.backgroundColor = this.graphics.newBrush (this.graphics.BrushType.
                SOLID_COLOR, valid ? [1, 1, 1, 1] : [1, 0.5, 0.5, 1]);
                w.ok.enabled = valid;
                var valid2 =  /^[\d]+$/.test (w.input.text)
                if (!valid2){
                // https://stackoverflow.com/questions/27772805/need-a-regex-to-remove-everything-except-numbers
                w.input.text = w.input.text.replace(/\D+/g, "");
                // https://stackoverflow.com/questions/596481/is-it-possible-to-simulate-key-press-events-programmatically
                w.input.dispatchEvent(new KeyboardEvent('keydown', {'key': 'End'})); // Keyboard Event  does not have a constructor.
                    }
                var valid3 = w.input.text.length<7;
                if (!valid3){
                    w.input.text =w.input.text.substring(0,7);
                    }
            }
            w.clear.onClick = function(){
                var valid = /^[\d]+$/.test (w.input.text) && w.input.text.length===7;
                if (valid){
                    if (confirm("Do you want clear the input field") ===false) {
                        return;
                        }
                    }
                w.input.text = "";            
                }
 w.show();

What this does:

 

If you type anything other than a number, the code deletes the non-numeric items. If you add numbers after the length is 7, the code returns the first 7 characters. If I could simulate pressing the "End" key, it would work fine.

Unfortunately, it moves the cursor back to the beginning after it does the replacement - for example:

  • If I type 1234A567, I should get 1234567, but I get 5671234.
  • If I type 123456789, I should get 1234567, but I get 9123456.

If I could simulate sending the end key or moving the cursor to the end of the field, it should work.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
May 31, 2023 May 31, 2023

Copy link to clipboard

Copied

I'm not JS expert and definitely not on InDesign's ScriptUI implementation - prefer VB6 - but from quick googling - javascript textfield cursor position - there is a selectionStart and selectionEnd for text fields in JS:

 

http://help.dottoro.com/ljtfkhio.php

 

Supported by objects:
HTML elements:
input:password, input:search, input:text, isindex, textarea

 

Or even more:

https://www.geeksforgeeks.org/how-to-place-cursor-position-at-end-of-text-in-text-input-field-using-...

 

TextRange.moveEnd(): It helps to move the end of the range by a specified number of units.
TextRange.moveStart(): It moves the start of the range by a specified number of units.

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
May 31, 2023 May 31, 2023

Copy link to clipboard

Copied

Thanks, Robert, but those properties aren't available in ScriptUI.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Dec 29, 2023 Dec 29, 2023

Copy link to clipboard

Copied

@Marshall_Brooks 

 

Could i ask what OS you are using?
I remember i believe seeing this work on OSX. Ive now tried your examples on Windows and the input editText simply does not change color. I bumped into this after research the ScriptUI for Dummies PDF by Kahrel

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Jan 02, 2024 Jan 02, 2024

Copy link to clipboard

Copied

I'm using Windows 10 Enterprise Version 22H2 and it works for me in FrameMaker 2019 and FrameMaker 2022. Note that I don't have access to InDesign, so I can't verify whether or not it works in that application.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jan 02, 2024 Jan 02, 2024

Copy link to clipboard

Copied

Ah i see, in photoshop 21 and 23 it doesnt work for the text highlight. Like @Marc Autret states, i was wondering perhaps its an OSX thing where it still works.

 

Also tried Illustrator, there the input show white, but does not turn red on errors

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Jan 02, 2024 Jan 02, 2024

Copy link to clipboard

Copied

LATEST

Thanks for testing and clarifying. I remember previous comments that is was no longer supported and was pleased to see it still works in FrameMaker.

 

Hopefully, Adobe will re-enable it in the other programs, but I'm not holding my breath. (Hopefully that's re-conditionalize paragraph tags in FrameMaker like it used to have in FM 11, but that hasn't happened so far either - OT).

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines