Jump to content

All Activity

This stream auto-updates

  1. Today
  2. @bernhard I wasn't able to provide a more robust response above but I wanted to mention that my concept wasn't an impulsive decision. I think my language in the confirmation popup should be changed. The "clear all" feature is a shortcut to clear all content in all languages including the default language so that it has one purpose. One of the things that I first had to consider was how often the feature gets used. Fluency has been available for over 5 years and this feature absolutely has great value but hasn't been mentioned before. I haven't heard about users/devs expressing a need to clear all content from multilanguage fields, and the general practice has been in my experience that if content changes then it would involve entering new content and re-translating. I thought about weighing how often the feature would be used compared to translation and it seemed like much less often, but I didn't have experience to go on. I also wondered if this was a feature that was use most during the initial build and population of content vs. ongoing content management. I think that was another area where the use of the feature may change over time as far as how often it's used, but again I haven't had the need so my understanding may be limited there. I'm not against the concept of a always-visible button or icon. Throughout the admin UI the trash icon is, as far as I can tell, mainly implemented for block, group, or object deletion rather than field contents. This is where the use of the trash icon without a label to explain the difference between what it means when used specifically for a field could be confusing because it does break from what it signifies elsewhere. The position of the translation action menu to the right as a clickable option felt natural with destructive actions located elsewhere. Placing it under a dedicated language icon menu seemed like it would strongly associate the action exclusively with multilanguage field features. Each field can have translation disabled, so there would have to be a decision whether to include just a trash icon even if there is no translation enabled for consistency when working with multilanguage fields. I think this could be a great convenience enhancement even if it steps outside of Fluency's current purpose and may be even be necessary for consistency. This may present another challenge because only multilanguage fields would provide the ability to delete content from the primary field that is visible where all other fields that only have primary content would need to manually be deleted. It's a step that may push a little further into ProcessWire's core behavior because some fields enable you to delete content for the default language alone, but other fields that are not multi-lingual still require a select-all-then-delete action by the user. If the trash icon is not present on all fields, would it seem like an oversight? Another thing that comes to mind is whether a multilanguage field with translation disabled were next to a single language field. On the left you can delete the primary content without deleting any other language, but on the right you can't delete the primary content with one click. The tabs hint that it's related to language fields, but non-language fields aren't afforded the same convenience even though they are visually similar to the end user. The intention is to have the clear all button clear everything in one action, so if the user is aware of the feature then they wouldn't clear any text to begin with. I think the language I used in my original "confirm" example may not have been as descriptive as it should be. It's an all-or-nothing action. The wording you used: "Are you sure you want to clear content in all languages for this field?" is much better and communicates clearly. So my approach was placing the option under an action menu since it felt natural to group secondary behaviors. The button that opens the translator is secondary and the ability to delete content in all languages is secondary compared to the primary feature of translation. I like the ideas and want to take the best approach, and I'm not against any specific implementation. Thinking through this though when you originally mentioned stepping into "core" behavior may be more impactful than it seemed when putting all different types of fields into the mix.
  3. Yesterday
  4. I sketched my ideas to come back and share them with the people who suggested the feature and I haven't worked on any true implementation yet. I can assure you there's no rush, I wanted to get some feedback before diving in and coding everything. If I had made a decision in private, programmed everything, then come back and announced the new changes it would be far more work to change my approach if it had a negative reception. Luckily the custom header actions are 20 lines of code and aren't a commitment.
  5. Hi @bramwolf that's great! Great you found the reason for the issue. The multisite module is a dinosaur and has not seen any updates in 7 years. Of course in the ProcessWire universe this does not have to mean anything and soma for sure knew what he was doing, but I tried the module several times and always got into trouble somewhere. Rewriting page paths is a huge change and 3rd party modules (like RockCommerce) might need to account for such situations (or the other way round). So to be clear hear, I'm not going to even try to officially support the usage of RockCommerce together with any unofficial multisite module. I have written my own RockMultisite module and I'll likely never share it with the public because there are too many possible pitfalls. Having said that, of course I do try to help you and if there is anything I can do to make it work for you without breaking anything for anybody else I'm happy to do that! But unfortunately I don't understand what you are trying to say here: --- Thx for that question. I was totally thinking that I mentioned that in the video, but turns out that I had the RockCalendar module in my mind. See this section of the showcase video: https://youtu.be/40h6I4i8JKs?si=rTlUzI95f8tH4dNr&t=254 This is exactly the same case/reason for RockCommerce and this shows why it's a separate module. Because I need the features/UI for multiple other modules. It seems that this information was missing for RockCommerce and I'm sorry for that. I updated the docs here: https://www.baumrock.com/en/processwire/modules/rockcommerce/docs/product-variations/ If you have any further questions let me know.
  6. Hm. Really appreciate your work on this! Thank you very much. But as much as I understand and agree with this: I really don't think that it is intuitive for users to look for the "wipe all" at the opposite direction of the button that does the same thing when filling fields ("translate to all"). My point is: We all understand the necessity of deleting all fields. And we might think of it when we need to reset a field. But the average user? Do you really think he/she will clear the text field, then head over to the translation icon, then click clear all? I think what will happen is that he/she clears the field (deletes "Home" in your screenshot), hit's save and doesn't even think of deleting it in other languages. Maybe if he/she does, then I think it's more likely that he/she will look for it where he/she always looks for it. Below the field where it says "translate to all". In our project that's what everybody really got used to. Fill in the field, hit translate, hit save. I honestly and strongly think that the "delete all" feature/button should be close to the "translate all" button. I get your point about a cluttered UI, but what if a button to "delete all" only appeared if one of the non-default languages are filled and the default language get's cleared out? It would not clutter the UI, it would appear as the user clears the default language value and it would probably remind the user to also clear the other language's values. What I could also think of is something like this: Here I'd see the challenge to differentiate between users that case where someone wants to only delete this languages value or all languages values. Another idea would be to instead of this: Show a popup as soon as the default language value gets deleted that shows something like: "Delete the content of all other languages as well? Cancel / OK" Please don't hurry with a solution and take your time ๐Ÿ™‚ I think a big part of making this feature as useful as possible is not only about providing a way to do it but also about showing/reminding the user that going this way might be necessary (to not end up with orphaned content).
  7. Thank you @teppo this is so helpful. I am trying your hook code above, but it says undefined method returnResultsJSON() after placing it in /site/ready.php Strange. I see that the method is referenced at the top @method in the module code. What am I not understanding ? Edit: I ended up getting this to work by creating /site/templates/search.php with the following code: <?php namespace ProcessWire; if ($config->ajax) { // AJAX request, send JSON header, output JSON and exit header('Content-Type: application/json; charset=utf-8'); echo $modules->get('SearchEngine')->renderResultsJSON(); exit; } Crucially, I forgot to create a new template 'search' and a new page using this template in the PW Admin Panel, (the page is titled 'Search', and set as a Hidden Page). After that, I had to modify my markup to use the search page endpoint as the url. header.latte (where the search bar markup lives), passing options to the /search/ endpoint url <div class="uk-visible@m"> {var $searchengine = $modules->get('SearchEngine')} {* Pass the form_action parameter to point to your search.php file *} {var $formOptions = ['form_action' => $config->urls->root . 'search/']} {$searchengine->renderForm($formOptions)|noescape} {$searchengine->renderResults()|noescape} </div> The Javascript @teppo provided, with a modified fetch query: const findResults = () => { window.clearTimeout(searchTimeout) searchTimeout = window.setTimeout(() => { if (searchResults) { searchResults.setAttribute('hidden', 'true') } if (searchInput.value.length > 2) { if (searchCache[searchInput.value]) { renderResults(searchForm, searchCache[searchInput.value]) return } if (searchInput.hasAttribute('data-request')) { return } searchInput.setAttribute('data-request', 'true') searchInput.setAttribute('disabled', 'true') const searchParams = new URLSearchParams() searchParams.append('q', searchInput.value) fetch(`${pwConfig.rootUrl}search/?${searchParams}`, { headers: { // set the request header to indicate to ProcessWire that this is an AJAX request; this // way we can check $config->ajax in the template file and return JSON instead of HTML // by calling $modules->get('SearchEngine')->renderResultsJSON() 'X-Requested-With': 'XMLHttpRequest', }, }) .then((response) => { if (!response.ok) { throw new Error('Network response was not ok') } console.log(response); return response.json() }) .then((data) => { searchCache[searchInput.value] = data renderResults(searchForm, data) searchInput.removeAttribute('data-request') searchInput.removeAttribute('disabled') searchInput.focus() }) .catch((error) => { console.error('Error fetching search results:', error) searchInput.removeAttribute('data-request') searchInput.removeAttribute('disabled') searchInput.focus() }) } }, 300) } and an additional <script> tag in _main.php to allow the above javascript to reference the ProcessWire root properly within my MAMP local dev environment <!-- Create a global object to store ProcessWire paths for ajax search hook --> <script> var pwConfig = { rootUrl: '<?= $config->urls->root ?>' }; </script> Maybe this is not the most elegant way to do this, but it seems to be working nicely now. I spent too many hours trying other ways. Maybe someone could point out redundancies or a better methodology. Sharing this here in case anyone else wants to add some nice AJAX functionality to @teppo's fantastic module. Thanks!
  8. I'm currently having this issue and fixed it with this hook: /** * CSS: Hide the empty height space of inputfield columns when they stacked (mobile / tablet) on ProcessPageEdit * */ $wire->addHookAfter('ProcessPageEdit::execute', function(HookEvent $event) { $event->return .= " <style> @media only screen and (max-width: 767px) { .maxColHeightSpacer { display: none; } } </style>"; });
  9. Last week
  10. will there be AVIF support?
  11. I'm adding a header action with something similar to the following: Inputfields.addHeaderAction('title', { label: 'Some custom actions', icon: 'fa-question', menuItems: [ { label: 'Option 1', href: 'https://somedomain.ddev.site/admin/page-to-show/', modal: true, active: true, callback: null, }, { label: 'Option 2', callback: function() { ProcessWire.alert('Shows option 2'); }, active: true, }, { label: 'Option 3', callback: function() { ProcessWire.alert('Shows option 3'); }, active: true, }, ] }); When there is a combination of types of actions then the options that execute a callback will trigger the option that executes a href/modal. When clicking the option that opens a modal, the other options are not triggered. Example that opens the "Logs" page in a modal. Clicking "Option 2" executes both the callback and opens the modal in "Option 1": The same occurs if "Option 3" is clicked. I'm trying to figure out if this is something I'm doing wrong or if there's a bug when mixing option types. I was able to create a workaround by adapting this code by @bernhard that uses a callback to programmatically open a modal: Inputfields.addHeaderAction('title', { label: 'Some custom actions', icon: 'fa-question', menuItems: [ { label: 'Option 1', active: true, callback: function() { const link = document.createElement("a"); const $link = $(link); $link.attr('href', 'https://moduledev.ddev.site/admin/setup/logs/'); $link.addClass('pw-modal'); $link.on('click', pwModalOpenEvent); $link.on('pw-modal-closed', () => $link.remove()); $link.click(); }, }, { label: 'Option 2', callback: function() { ProcessWire.alert('Shows option 2'); }, active: true, }, { label: 'Option 3', callback: function() { ProcessWire.alert('Shows option 3'); }, active: true, }, ] }); This works but doesn't use the native API. Anyone have any insights that could help? Many thanks!
  12. @bernhard I like your thinking ๐Ÿค @Tiberium I'm glad you chimed in as well. Good to know that there are many people that will benefit from a new feature. Here's the UI I'm working towards: Going to create a new header action for translation related features. This will keep the UI tidy, clear all will be enabled by default, and doesn't need an option to enable/disable it on the module config page. @bernhard The icon to show the translator will be moved from the icon next to the translation button to the menu for consistency, since it's hover there won't be extra clicks. It will of course politely ask you if you are sure you want to nuke all of the content... I like this implementation because it will provide a good location for possible future features as well without crowding UI for fields. It will take a little bit to implement, I'm trying to keep up with work and I think I caught a bug with the custom header actions in PW core. I'll tag you both when it's on the dev branch so you can take it out for a test drive.
  13. In my last post I said that I don't know what output strategy you are using, but from this I would assume that you are actually using RockFrontend. And thus that templating language you are using is probably Latte. To be honest I have no idea how you would do this in a RockFrontend-native way, but "AJAX endpoints" sound like a potential answer. Personally I would probably just go with an URL hook in this case, as it is pretty much "universal" for any ProcessWire site, regardless of what output strategy they are using. Gave my example in above post a quick try and it seems to work fine. URL hooks are awesome ๐Ÿ™‚
  14. @protro, this is an area where it is very difficult to give exact answers, but I'll try. Now, the reason I can't give 100% solid answer is that I don't know what kind of output strategy you are using, etc. And from your example I can see that you are using some templating language, which potentially complicates things a bit. The gist of it is that $searchengine->renderResultsJSON() won't remove page markup, it will just render JSON blob. If you were using a plain "direct output" output strategy, sample code could look something like this: <?php namespace ProcessWire; // /site/templates/search.php if ($config->ajax) { // AJAX request, send JSON header, output JSON and exit header('Content-Type: application/json; charset=utf-8'); echo $modules->get('SearchEngine')->renderResultsJSON(); exit; } ?> <html> <body> <!-- This is your normal HTML output --> <?= $modules->get('SearchEngine')->renderResults() ?> </body> </html> How to apply this to your particular site / output structure depends ๐Ÿ™‚ In some cases it could be easier to implement this as a separate endpoint, e.g. using URL hooks. Perhaps something like this: <?php namespace ProcessWire; // /site/init.php or /site/ready.php $wire->addHook('/json/search/', function($event) { header('Content-Type: application/json; charset=utf-8'); return wire()->modules->get('SearchEngine')->renderResultsJSON(); }); In this case you would modify the code so that it sends JS fetch requests to this URL instead. Note: not tested, written in browser, may not work. But that's the general idea.
  15. Thank you @FireWire and @teppo for your help. Before I attempt an htmx approach, I wanted to see if I could get @teppo's javascript working. It seems I am almost there, but curious why $modules->get('SearchEngine')->renderResultsJSON() is returning the full HTML page (<!DOCTYPE โ€ฆ) in the Response (verified in Inspector -> Network Tab). Status code is 200 and X-Requested-With shows XMLHttpRequest. I incorporated the javascript code from the gist that was referenced, and verified with console.log messages that it is identifying the searchForm and other elements. This is my template code where SearchEngine is invoked (I am also calling the styles and scripts in the <head> as you reference in the module documentation). <div class="uk-visible@m"> {var $searchengine = $modules->get('SearchEngine')} {$searchengine->renderForm()|noescape} {if $config->ajax} {$searchengine->renderResultsJSON()|noescape} {/if} </div> I am using MAMP serving at http://localhost:8888/mysite/ Do I need to pass additional formatting to the renderResultsJSON as you have in the example here ? Thank you for looking this over,
  16. I got yesterday exact this question from a client. He has 5 different languages. When he corrects one word, it is not very comfortable to update the other translations. Personal, I don't have a strong opinion if that has to be default or not. I have to go to the config screen of fluency anyway, to setup it.
  17. @teppo & @FireWire โ€” Thanks to you both! I understand this is not finished code, but my biggest obstacle with developing is when Iโ€™m using multiple tools that Iโ€™m only passingly familiar with, and a working (if rough) solution is exactly what I need to start with. Iโ€™m looking forward to this!
  18. I like to live dangerously* *please don't live dangerously Thank you for the correction of my oversight @teppo. I've edited my example to include that in case someone doesn't make it further down the comments ๐Ÿ‘
  19. How would a "clear all" button would be in the way of anybody? I really think that this is a must have feature. My client said so too. Would be interesting to hear other opinions, but for me I vote for enabled by default ๐Ÿ™‚
  20. Same thing here, though so far only seen it on one site. Not sure if it was an isolated issue or if it just hasn't come up yet on other sites ๐Ÿ™‚
  21. HTMX is definitely a good solution for this! Just a minor note, even though I do get that this was meant as a rough example: be sure to always sanitize user provided input before output ๐Ÿ™‚ <p>There were no search results for "<?=$sanitizer->entities1($input->get->q)?>"</p> As an alternative to rendering HTML in the backend, SearchEngine provides an option to output JSON, but that way you'll have to write a bit more JS. Querying data with fetch is easy, but you'll also need to rende results, handle debouncing, etc. I've been thinking of adding a prebuilt solution for that into the module itself. In the meantime here's a gist that shows one ajax search approach โ€” just hacked it together quickly, so there are likely things that I've forgot to account for, but it seems to work based on a quick test on one of my sites ๐Ÿ™‚
  22. I was about to try this exact approach @FireWire. Thank you for a leg up! โ™ฅ๏ธ
  23. @bernhard I was working on some blocks in RPB and noticed that there were some edge cases where changes weren't being detected by RPB. I ran into this with the trash icon on RepeaterMatrix items. Clicking it checks a hidden checkbox programmatically and doesn't emit a change event, so it's pretty much invisible to everything else that isn't RepeaterMatrix code. I added this mutation observer to InputfieldRockPageBuilder.js to watch for elements that have had the 'InputfieldStateChanged' class added. // trigger changed event when InputfieldStateChanged is added to elements $(document).on('ready', function () { const rpbItemsObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.target.classList.contains('InputfieldStateChanged')) { RockPageBuilder.changed(mutation); } }); }); $('.rpb-items').each(function (_, rpbItem) { rpbItemsObserver.observe(rpbItem, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); }); }); In my testing this took care of some edge cases where changes aren't visible to RPB. Could be helpful for handling other sneaky inputs as well ๐Ÿ‘
  24. @protro I use this module with htmx and it works nicely. It's pretty easy to do and it can all be done in HTML without handwriting JavaScript or parsing JSON. Here's a simple example. It should work, it may need tweaking but the concept is accurate. <!-- Your search form --> <style> .search-box { position: relative; } .search-indicator { align-items: center; display: flex; inset: 0; justify-content: center; opacity: 0; pointer-events: none; transition: opacity .3s; } /* Style your AJAX indicator as you please */ .htmx-request .search-indicator, .htmx-request.search-indicator { opacity: 1; pointer-events: auto; } </style> <div class="search-box"> <form hx-get="the search URL here" hx-target="#search-results-container" hx-disabled-elt="button[type=submit]" hx-indicator=".search-indicator"> <input type="search" inputmode="search" name="q" placeholder="What would you like to find?"> <input type="submit" value="Search"> <div id="search-results-container"> <!-- Search results will load inside this div --> </div> </form> <div class="search-indicator"> <span>Searching...</span> </div> </div> hx-get tells htmx to request the page from the URL you provide hx-target tells htmx where to put the markup that it receives back from the AJAX request, it accepts any CSS selector hx-disabled-elt directs htmx to disable the submit button during the request to prevent people from clicking the button multiple times, it accepts any CSS selector hx-indicator isn't required, but tells htmx which element to add the .htmx-request class while the request is in flight and then remove it when the results are loaded for a nice "loading" display. It accepts any CSS selector On the search page all you have to do is render the results that will appear inside #search-results-container, just markup. This is a rough example, but just illustrates how simple it can be. You'll have to tailor this to your needs. You can probably use markup that the SearchEngine module generates, but I haven't used that before myself. <div class="search-results"> <?php if ($searchResults->count()): ?> <ul> <?php foreach ($searchResults as $result): ?> <li> <p><?=$result->title?></p> <a href="<?=$result->url?>">View Page</a> </li> <?php endforeach ?> </ul> <?php else: ?> <p>There were no search results for "<?=$sanitizer->entities1($input->get->q)?>"</p> <!-- Thanks to teppo for reminder to sanitize --> <?php endif ?> </div> That's the only markup you need. You'll probably need to do some additional work to make things like paging happen, but it's all doable. I cobbled this together based on some code I've already written so it's just a starting point and probably needs some tinkering. Here's a handy reference of htmx attributes that will let you do anything you need.
  25. I do want to keep the scope of Fluency focused, but I'm comfortable with extending what it provides modestly. It would be a similar to how Fluency adds the content change indicators to tabs. It isn't something specific to translation functionality, but because Fluency provides the ability to do "mass translation" across multiple languages it's something that is helpful. I think that a "clear all" type of button is something in the same spirit. If Fluency can generate large amounts of content, it may be useful to have an extra tool to make working with it a little easier. It's the "enhancement" part of the "translation enhancement suite for ProcessWire" ๐Ÿ˜Ž ha! I am weary of going too far beyond and making Fluency intrusive. Maybe adding it as a config option, off by default, to enable that can be helpful and stay out of people's way.
  26. Has someone put together a tutorial for displaying search results via AJAX requests? I've been finding this pattern a bit challenging, not having done much with AJAX. I see that the module returns JSON, and that javascript can be used to return what I see rendered at the static page mysite.com/?q=mySearchTerm โ€ฆ I'm not exactly sure how do to it, my working code is simply: <div class="uk-visible@m"> {var $searchengine = $modules->get('SearchEngine')} {$searchengine->render()|noescape} </div> I see that Rockfrontend also provides AJAX endpoints. Some combinations of these approaches? Any help would be appreciated.
  27. Yeah I was a bit surprised and almost already asking, but also was too busy with other stuff ๐Ÿ™‚ That would be great! I was kind of expecting that it was intentional, but at the same time it is really a pain if you want to wipe content from a multilang field, especially if you have more languages (as you need several mouse actions for each language when all you want is to clear the field). Also I can see that it might not be obvious to some that to clear the field in all languages one would have to clear the field in one language and then hit "translate to all". So I think an additional button would be a great ui improvement. But I'm wondering... how would ProcessWire handle that without fluency. Wouldn't that either be a core improvement than an additional feature of Fluency?
  28. @bernhard I had a response typed out, didn't click "Submit Reply" and just came back find out that you never saw it ๐Ÿคฆโ€โ™‚๏ธ Not clearing is intentional. The only way Fluency works right now is to take the source content and make the request to ProcessWire, pull from cache, or make a call to the translator API, then send it back to the client and replace the contents. At one point I think it may have cleared it but I updated the module to prevent taking any action if the source field is empty since it saved a round trip and an empty call to the translation API. Since Fluency is for translating and adding some nice-to-have things like the content change indicators on the tabs, maybe a secondary button off to the right to "Clear all content" would be useful? That could be a more intentional UI feature. What do you think? Worth doing?
  1. Load more activity
ร—
ร—
  • Create New...