Search code examples
javascriptadobeadobe-indesignextendscript

inDesign Scripting - Cut from "typesetting script" TextFrame and paste on empty TextFrame


I need an Adobe Script that do this:

  • Select TextFrame with the whole Text Script (page1, text, page2, text and so on);
  • Cut a paragraph (usually separated by a \r or ^p);
  • User selects or create a new TextFrame;
  • Paste the line cut before on the new empty TextFrame; and
  • Go to the next paragraph.

I've been doing this typesetting job and I came across this Typesetterer for Photoshop, went on a journey to find a similar script/application for inDesign and I found this this solution, tried it and worked, boosting my productivity by at least 2x. Tried this split one but it takes way longer to resize and move all the generated TextFrames. But it was really useful in developing my final code.

The main problem with this approach is that it required the typesetting script to be in a .txt file, which breaks all Bold and Italic from the original script.

main();

function main() { // Main() just to keep it organized
  try {
    var myMeasuringUnit = app.scriptPreferences.measurementUnit;
    app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
  } catch (exception) {
    alert(exception.description);
    exit();
  }

  var myTextFrameSelection = app.activeDocument.selection;
  cutAndPaste(myTextFrameSelection);

  try {
    app.scriptPreferences.measurementUnit = myMeasuringUnit;
  } catch (exception) {}
}

function cutAndPaste(myTextFrameSelection) {
  for (elementsInSelection = 0; elementsInSelection < myTextFrameSelection.length; elementsInSelection++) {
    if (myTextFrameSelection[elementsInSelection].constructor.name == "TextFrame") {
      var myParagraphs = myTextFrameSelection[elementsInSelection].texts[0].paragraphs;
      var isCopied = true;
      for (var i = 0; i < myParagraphs.length; i++) {
        try {
          myParagraphs[i].insertionPoints.itemByRange(0, -2).select();
          app.cut();
          isCopied = true;
        } catch (exception) {
          isCopied = false;
        }
        if (isCopied) {
          /*
          Attempt at adding a way to select another TextFrame and app.paste() the previously app.cut() line in it

          var doc = app.activeDocument.selection[0];
          doc.addEventListener('afterSelectionChanged', selectionChanged);

          function selectionChanged() {
              if (doc.selection[0] instanceof TextFrame && doc.selection[0].contents == '' && doc.selection[1] == null) {
                  if (doc.selection[0] instanceof TextFrame) {
                  doc.selection[0].contents = app.paste();
              }
          }
          */
          app.paste(); // This just pastes it in the same folder
        }
      }
    }
  }
} // For now, what it does it "cut paragraph from the TextFrame but also paste in the same TextFrame"

So far, it just cut from the TextFrame, paragraph after paragraph, and it pastes on the same TextFrame. It's close to doing what I need, but I can't seem to find a way to select a new Frame while in the main loop. I commented out the possible solution that I'm struggling to get to work.

Maybe I'm using the wrong cut/paste functions too? Maybe it is easier to modify the 2nd link solution to accept .docx?


Solution

  • Important difference between RisingFog's solution and Eugeny Budantsev solition is the interaction with user. Former script shows the dialog window and keep it running until you close the window. Later runs just once and can't keep tracking user manipulations.

    So if you want to keep your script running (and doing something as reaction on user manipulations) you need to show a some window even if the window will be empty. Something like this:

    #targetengine session
    
    var dialog = new Window("palette", undefined, undefined);
        dialog.preferredSize.width = 200;
        dialog.preferredSize.height = 50;
        dialog.text = "Select next frame";
        dialog.onClose = function() {
            doc.removeEventListener('afterSelectionChanged', selectionChanged);
        }
    
    dialog.show();
    
    var doc = app.activeDocument;
    doc.addEventListener('afterSelectionChanged', selectionChanged);
    
    function selectionChanged() {
    
        // here is the code that will run wenever you change selection
    
        if (doc.selection[0] instanceof TextFrame
            && doc.selection[0].contents == ''
            && doc.selection[1] == null) {
            alert('Next empty text frame is selected!')
        }
    }
    

    Of course you can fill the window with some texts or buttons.

    Update

    Here is the example how you can cut first paragraph from selected non-empty text frame (suppose it's the Text Script) and paste this paragraph automatically into next selected empty frame:

    #targetengine session
    
    var dialog = new Window("palette", undefined, undefined);
        dialog.preferredSize.width = 300;
        dialog.preferredSize.height = 50;
        dialog.text = "Please, select next frame";
        dialog.onClose = function() {
            doc.removeEventListener('afterSelectionChanged', selectionChanged);
        }
    
        dialog.add("statictext", undefined, "Last cutted text:");
    
    var msg = dialog.add("edittext");
        msg.text = "";
        msg.characters = 40
    
    dialog.show();
    
    var doc = app.activeDocument;
    doc.addEventListener('afterSelectionChanged', selectionChanged);
    
    function selectionChanged() {
    
        var sel = doc.selection[0];
    
        if (sel instanceof InsertionPoint) sel = sel.parent.textContainers[0];
    
        if (sel instanceof TextFrame || sel instanceof Story) {
    
            if (sel.contents != '') {
                app.select(sel.paragraphs[0], SelectionOptions.REPLACE_WITH);
                msg.text = sel.paragraphs[0].contents;
                app.cut();
                app.selection = null;
            }
            else {
                sel.texts.everyItem().select();
                app.paste();
                app.selection = null;
    
                // remove '\r' at the very end of text in the frame
                app.findGrepPreferences.findWhat ="\r$";
                app.changeGrepPreferences.changeTo = "";
                sel.changeGrep();
    
            }
        }
    }
    

    enter image description here

    enter image description here

    Update 2

    Here is the same script. Only difference, you don't need click on Text Script frame anymore. You need select the text frame before you run the script and then the script will cut first paragraph from this frame automatically every time when you click on any empty text frame.

    #targetengine session
    
    var dialog = new Window("palette", undefined, undefined);
    dialog.preferredSize.width = 300;
    dialog.preferredSize.height = 50;
    dialog.text = "Please, select next frame";
    dialog.onClose = function() {doc.removeEventListener('afterSelectionChanged', selectionChanged)}
    
    dialog.add("statictext", undefined, "Last copied text:");
    
    var msg = dialog.add("edittext");
    msg.text = "";
    msg.characters = 40;
    
    // --- here is the Pause button
    var pause = false;
    var pause_btn = dialog.add("button", [10,10,300,30], "Pause");
    pause_btn.onClick = function() {
        pause = !pause;
        // if (pause) msg.text = "[PAUSED] " + msg.text; // it can be done better, but it works, too
        // if (!pause) msg.text = msg.text.replace(/^\[PAUSED\] /, "");
        pause_btn.text = pause ? "Resume" : "Pause"; // this is way better
    };
    // ---
    
    var doc = app.activeDocument;
    var master_frame = app.selection[0]; // <-- the Text Script frame
    
    if (master_frame instanceof TextFrame || master_frame instanceof Story) {
        dialog.show();
        app.selection = null;
        doc.addEventListener('afterSelectionChanged', selectionChanged);
    } else {
        alert("Please, select a frame with text before run the script!");
        exit();
    }
    
    function selectionChanged() {
    
        if (pause) return; // <-- here you check the state of the button
    
        var sel = doc.selection[0];
    
        if (sel instanceof InsertionPoint) sel = sel.parent.textContainers[0];
        if (sel instanceof TextFrame || sel instanceof Story) {
    
            if (sel.contents == '' && master_frame.contents != '') {
    
                app.select(master_frame.paragraphs[0], SelectionOptions.REPLACE_WITH);
                msg.text = master_frame.paragraphs[0].contents;
                app.cut();
                app.selection = null;
                sel.texts.everyItem().select();
                app.paste();
                app.selection = null;
    
                // remove '\r' at the end
                app.findGrepPreferences.findWhat ="\r$";
                app.changeGrepPreferences.changeTo = "";
                sel.changeGrep();
    
            }
    
            if (master_frame.contents == '') alert ("No text to paste anymore!")
        }
    }
    

    I've added 'Pause' button to the window.