1

In laravel 8 app with "jd-dotlogics/laravel-grapesjs": "^3.3.0", I need to extend its functionality :

  1. To add new custom button at toolbar

  2. By clicking on this custom button to open JS prompt method

  3. If prompt method was closed with success to save entered value(external url) with unique id of selected element in db with request to server.

  4. For any element on the form I need some unique custom_id and sending saving request save unique custom_id/Entered value

Next time when editor is opened by manager request to db must be send and these Entered value created prior sessions must be tied to any element by these unique custom_id. Can I create somehow these unique custom_id ?

Manually editing public/vendor/laravel-grapesjs/assets/editor.js file I made 3 first points(last without posting request to server). https://prnt.sc/rhIrgNpV_JS7 I know usually it is supposed that this file is not edited manually, but I do not see any other way to implement it.

Unpacked version is 37 K lines of code, so I pasted it in : https://zerobin.net/?bf6a96ac404ac3b1#oVMCXPuQUJVxzm9kuLAa/7tAQ9Kj1Gjhw0y/4aGR+BE=

Any ideas how can I create and use these unique custom_id ?

I need something like when html element is located on some element to assign unique custom_id = "parent_el_type_id=>parent_el_type_id=>this_el_type_id=>" - to be sure that this custom_id is unique

UPDATED BLOCK # 1;

I have got the common idea. I try to implement it as :

In config file config/laravel-grapesjs.php(which has links to all js files) I made changes :

'scripts' => [
    'https://www.jsdelivr.com/package/npm/fs.promises', // I tried to import fs.promises - but that does not work
    'vendor/laravel-grapesjs/assets/editor_control.js', // I added new file
    'vendor/laravel-grapesjs/assets/editor-config.js',
    'vendor/laravel-grapesjs/assets/modified_editor.js', // modified editor.js must be used
],

And new file vendor/laravel-grapesjs/assets/editor_control.js with lines :

import fs from "fs/promises";
import { v4 as uuidv4 } from 'uuid';

(async () => {


  let code = await fs.readFile("editor.js", "utf-8");  // source js file
  let regexp = /(document\.createElement\("[a-z]*"\))/gi;
  let match;

  while ((match = regexp.exec(code)) !== null) {
    let createElementString = match[0];
    let tagName = createElementString.split('"')[1];
    let startIdx = match.index;
    let endIdx = regexp.lastIndex;
    code = [code.slice(0, startIdx), `createElement("${tagName}","${uuidv4()}")`, code.slice(endIdx)].join('');
  }

  await fs.writeFile("modified_editor.js", code); // modified js file

})();

But I got 2 errors in console :

GET http://landigator.test/vendor/laravel-grapesjs/assets/modified_editor.js net::ERR_ABORTED 404 (Not Found)
and
Uncaught SyntaxError: Cannot use import statement outside a module (at editor.js:1:1)

The reason is that I can not use import function in plain js file. I know how to use fetch function and it could be replacement of "fs/promises", but what can I use instead of uuidv4?

I think I must open modified_editor.js AFTER editor_control.js was run and created modified_editor.js file

How that can be fixed ?

UPDATED BLOCK # 2: I failed to upload fs/promises, so I create table in db with 2 fields try to save source_code - with source of js file and generated_code - in I want to save converted file.

I made control method for converting from source_code into generated_code :

$jsSourceFile = JsSourceFile
    ::getByCodeKey('editor.js')
    ->first();
return view('pages.editor-modify-script', [
    'jsSourceFile'=> $jsSourceFile
]);

and in blade file I try to use this value from db :

            console.log('-BEFORE code::')
{{--            let code = "{{ htmlspecialchars($jsSourceFile->source_code) }}";--}}
            let code = '{{ $jsSourceFile->source_code }}';
            console.log(code)

            let regexp = /(document\.createElement\("[a-z]*"\))/gi;
            let match;

            while ((match = regexp.exec(code)) !== null) {
                let createElementString = match[0];
                let tagName = createElementString.split('"')[1];
                let startIdx = match.index;
                let endIdx = regexp.lastIndex;
                code = [code.slice(0, startIdx), `createElement("${tagName}","${window.v4()}")`, code.slice(endIdx)].join('');
            }
            console.log('-AFTER code::')
            console.log(code)

But I got error :

Uncaught SyntaxError: Invalid or unexpected token (at

and I see in browser : https://prnt.sc/ool9YWp85uWH If there is a safe way to pass laravel var $jsSourceFile->source_code into JS var ? I tried several ways, but failed.

Thanks!

mstdmstd
  • 2,195
  • 17
  • 63
  • 140
  • 1
    I don't see any other problem than generating some random id, which you can simply do by generating a [UUIDv4](https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid) or [UUIDv5](https://stackoverflow.com/a/65787016/2284136) string. UUID generation is relatively simple, so if you want / need to do it on PHP side instead, [it's no problem](https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid). UUIDv5 is useful, if your ids need to be in a client-server sync, though it sounds like you don't necessarily need that here. – Swiffy Jun 27 '22 at 07:26
  • 1
    For a problem like this, I don't think collisions will be a problem either, even with UUIDv4, as [the probability to find a duplicate within 103 trillion version-4 UUIDs is one in a billion.](https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions) – Swiffy Jun 27 '22 at 07:27
  • @Swiffy, thaks for pointing at uuidv4/uuidv5. They seems quite good. But : 1) I need to assign any element which can be selected by user with custom_id = “uuidv4 value” I provided zerobin link, any hint ? “.createElement(” found 80 and 37 K lines of code. I tried to review it, but it is too complicated 2) Next time when editor is opened by manager they must have the same custom_id = “uuidv4 value” of prior time I do not think that using of random uuidv4/uuidv5 could be a decision here. any time when editor is opened it must have THE same value... – mstdmstd Jun 27 '22 at 13:59

1 Answers1

3

Well if I understood correctly, this is actually a somewhat of a metaprogramming issue.

So we want to do something to all document.createElement instances in the huge chunk of code, mainly, to add some id to all of those created elements.

The first problem here is that there actually is no meaningful way to do something like:

document.createElement("div", { id: "uuid" });

And we also can't do:

document.createElement("div").setAttribute("id", "uuid");

Because setAttribute returns undefined instead of the element.

But first things first. Our approach here is to make a simple node.js script which loads the code file in as texts, finds all instances of document.createElement and replaces them with something that adds the id information.

But what that something is then? Well I figured the easiest approach would probably be to just make our own createElement function which accepts an id on top of the element tagname:

const createElement = (el, id) => {
    let element = document.createElement(el);
    element.setAttribute("id", id);
    return element;
}

All we have to do now is to replace all of the document.createElement calls with our own createElement(tagName, id) calls. As our custom function also returns the element, just like document.createElement does, everything should work fine.

Here's that code:

import fs from "fs/promises";
import { v4 as uuidv4 } from 'uuid';

(async () => {

    let code = await fs.readFile("file.txt", "utf-8");
    let regexp = /(document\.createElement\("[a-z]*"\))/gi;
    let match;

    while ((match = regexp.exec(code)) !== null) {
        let createElementString = match[0];
        let tagName = createElementString.split('"')[1];
        let startIdx = match.index;
        let endIdx = regexp.lastIndex;
        code = [code.slice(0, startIdx), `createElement("${tagName}","${uuidv4()}")`, code.slice(endIdx)].join('');
    }

    await fs.writeFile("id_file.txt", code);

})();

Given the massive code file named file.txt, it outputs a new file named id_file.txt, which looks like this:

idfile

These ids will only change if the file is generated again.

Now the important thing here is to add the createElement function into the generated file, or otherwise the code won't work. I think this will do:

enter image description here

Swiffy
  • 4,401
  • 2
  • 23
  • 49
  • Thanks, for your help. Please read UPDATED BLOCK # 1 – mstdmstd Jun 29 '22 at 07:58
  • 1
    The idea is that the script that modifies the js file is standalone and you only have to basically run it once. The script that does reading and writing of the editor file needs to be something like `index.mjs`, so with an `.mjs` extension or the imports won't work (and you'll have to use `require` instead. However, it's not a proper code of conduct in SO to update question with more questions, so if what I just said won't solve the issue you edited in, you'll have to make a new question for it or we can try to solve it here in the comments, if it's not a huge issue. – Swiffy Jun 29 '22 at 08:06
  • 1
    Your modified editor file does not use any new imports or anything, it should work simply as a drop-in replacement for the original one. – Swiffy Jun 29 '22 at 08:08
  • I tried to use "fs/promises" in js block of laravel blade file but got "Can't resolve 'fs/promises' ". Not shure in which way have I to install/use it ? – mstdmstd Jun 29 '22 at 15:01
  • 1
    Maybe you have old node.js version? I have no idea what laravel blade file is. This one you just drop into, say, `C:/editor-changer-script/index.mjs`, run `npm i uuid` and `node index.mjs`. The script I made has nothing to do with any of your existing projects or anything - the only thing it does is search for any file you give it for `document.createElement` strings, change them and output another file. It's not a library, it's a separate script/tool to make the file you wanted. [fsPromises](https://nodejs.org/api/fs.html#fspromiseswritefilefile-data-options) – Swiffy Jun 29 '22 at 16:10
  • 1
    If you really can't get fs/promises to work, you can change it to just `import fs from "fs"` and replace the `await readFile` and `await writeFile` with their sync versions for example, so `readFileSync` and `writeFileSync` – Swiffy Jun 29 '22 at 16:15
  • My app has : vagrant@homestead:~/code/app$ node -v v14.19.0 vagrant@homestead:~/code/app$ npm -v 8.5.2 vagrant@homestead:~/code/app$ php -v PHP 8.1.3 (cli) (built: Feb 21 2022 14:48:42) (NTS) Copyright (c) The PHP Group Zend Engine v4.1.3, Copyright (c) Zend Technologies with Zend OPcache v8.1.3, Copyright (c), by Zend Technologies vagrant@homestead:~/code/app$ php artisan --version Laravel Framework 8.83.18 – mstdmstd Jun 30 '22 at 05:04
  • I tried different way of importing fs I found (like on https://stackoverflow.com/questions/64725249/fs-promises-api-in-typescript-not-compiling-in-javascript-correctly branch) but in all cases I got error : ERROR in ./resources/js/bootstrap.js 20:0-36 Module not found: Error: Can't resolve 'fs' in '/home/vagrant/code/app/resources/js' – mstdmstd Jun 30 '22 at 05:04
  • Please take a look at UPDATED BLOCK # 2 – mstdmstd Jul 01 '22 at 04:22
  • I don't know how to make this any clearer other than reiterating that the file generation script has nothing to do with your project and it should not be part of your project. You could install node on any computer, generate the file using my script, take the generated file to a USB stick and replace the file in your code with it. In fact, you don't even need the script; someone could just generate the file and email it to you. – Swiffy Jul 01 '22 at 06:09