') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); [4.x] Add `isDirty` / `isClean` by ryanmitchell · Pull Request #5502 · statamic/cms · GitHub
Skip to content

[4.x] Add isDirty / isClean - #5502

Merged
jasonvarga merged 60 commits into
statamic:4.xfrom
ryanmitchell:feature/is-dirty-is-clean
Feb 27, 2024
Merged

[4.x] Add isDirty / isClean#5502
jasonvarga merged 60 commits into
statamic:4.xfrom
ryanmitchell:feature/is-dirty-is-clean

Conversation

@ryanmitchell

@ryanmitchellryanmitchell commented Mar 15, 2022

Copy link
Copy Markdown
Contributor

This PR adds a trait for isDirty() / isClean() support on Entries through the use of a trait. I set it up this way to allow it to be extended to other stores if the approach is felt to be correct.

Tests are failing as the test environment doesn't seem to like using $entry->fresh() - not sure how to get around that!

You can use the functions as follows:

$entry->isDirty();
$entry->isDirty('field');
$entry->isDirty(['field1', 'field2']);
$entry->isClean();
$entry->isClean('field');
$entry->isClean(['field1', 'field2']);

Closesstatamic/ideas#7

@jasonvargajasonvarga left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Although the toCacheableArray method is never used, I think that's a leftover relic from v2. I'm going to sneak the removal of it into 3.3, and you can make your own dedicated method in this PR.

@ryanmitchell

Copy link
Copy Markdown
ContributorAuthor

No problem - I've updated that method to be called getDirtyArray() (please come up with something better), and put some of the data logic in there.

Also separated out a separate getOriginal() seeing as the logic was there for it anyway.

jasonvarga
jasonvarga previously requested changes Mar 15, 2022

@jasonvargajasonvarga left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other classes, for example, Asset, we already have the concept of "original" data. I'd want to use that here. There's a trait, etc.

@ryanmitchell

Copy link
Copy Markdown
ContributorAuthor

Yep I can see the syncOriginal function - see how I've updated to look for getDirtyArray if it exists (seemed like the best approach). However, I've no idea where in the entry 'lifecycle' to insert syncOriginal() to get it to populate with any yaml-stored values?

@jasonvarga

Copy link
Copy Markdown
Member

That would need to go in the Stache where it initially makes an instance from the file. Right about here:

@ryanmitchell

Copy link
Copy Markdown
ContributorAuthor

Thank you! Thats updated now - let me know what you think.

@jasonvarga
jasonvarga changed the base branch from master to 3.3March 15, 2022 19:40
@ryanmitchell

Copy link
Copy Markdown
ContributorAuthor

Not sure why tests are failing on this (and they seem unrelated to the changes made). They are all passing for me locally.

@ryanmitchell

ryanmitchell commented Mar 22, 2022

Copy link
Copy Markdown
ContributorAuthor

@jasonvarga Struggling a bit with the tests on this one. On the two asset failures, do I just update the tests to expect the .meta files? Or do I handle it some other way?

 Make sure .meta is not generated by calling ->data() before it exists
@ryanmitchell

Copy link
Copy Markdown
ContributorAuthor

I've got all tests passing locally now, but I cant get them working here in GitHub actions. If you can see why that would be really helpful.

@duncanmcclean

duncanmcclean commented Mar 31, 2022

Copy link
Copy Markdown
Member

I've got all tests passing locally now, but I cant get them working here in GitHub actions. If you can see why that would be really helpful.

Looks like an error on GitHub's side, it doesn't get past setting up the jobs.. probably needs to be re-run.

image

@jasonvarga
jasonvargaforce-pushed the feature/is-dirty-is-clean branch from 0dbbf73 to ca783d6CompareFebruary 16, 2024 16:34
@jasonvarga

Copy link
Copy Markdown
Member

Thanks for this PR. Another banger. And sorry for the delay, it's pretty substantial.

I made a handful of changes:

  • Moved HasDirtyState into Statamic\Data where there are similar data-related traits.
  • Use HasDirtyState everywhere, and deprecate the existing SyncsOriginalState.
  • Ensures syncOriginal() is called at the end of save() so that events etc get the appropriate dirty state, and added tests for this.
  • Renamed getDirtyArray to a wordier but more explicit getCurrentDirtyStateAttributes so it doesn't get confused with getDirty which Eloquent models have and I imagine we might add down the road.
  • Add tests for isClean
  • Moved all the stache syncOriginal stuff to a consistent spot in one class - when it's created from the file or loaded from the cache.

Some additional notes:

  • The user events test should be testing creating/created but it would mean changing unrelated tests, so I just left a comment instead.
  • Since assets store "data" in a sub-array, you needed to add extra logic in isDirty to handle that. I'd prefer it to be a flat array like everywhere else but that would have made this PR grow in scope, and might've been a breaking change. We'll consider changing that for v5.

@ryanmitchell

Copy link
Copy Markdown
ContributorAuthor

Thanks for all the changes. As always you’ve made it much better.

@jasonvargajasonvarga changed the title [4.x] Feature: add isDirty / isClean[4.x] Add isDirty / isCleanFeb 27, 2024
@jasonvarga
jasonvarga merged commit 7da46d8 into statamic:4.xFeb 27, 2024
@ryanmitchell
ryanmitchell deleted the feature/is-dirty-is-clean branch February 27, 2024 20:12
@ryanmitchell

Copy link
Copy Markdown
ContributorAuthor

❤️

@edalzell

Copy link
Copy Markdown
Contributor

KA
BOOM

@afonic

Copy link
Copy Markdown

Thanks for this PR, it will be massively helpful.

Not sure where to ask so I'll try here! This breaks some tests for an addon of mine and I am struggling to understand why. For example this test: https://github.com/reachweb/locale-lander/blob/main/tests/Feature/LocaleLanderTest.php#L61 works fine if I remove the HasDirtyState trait from the Entry model (or downgrade to v4.50.0).

It seems that when using the trait, when checking for the translation here: https://github.com/reachweb/locale-lander/blob/main/src/Support/LocaleHelper.php#L63 it will return null. To make matters even worse, if I run the test 10 times, it will find the Entry 2 times out of 10 (or something like that).

Any ideas on what causes this and how to fix the tests?

Note that this only affects the test environment, the addon works fine in the actually Statamic install.

@duncanmcclean

Copy link
Copy Markdown
Member

@afonic I haven't dug into it but could your issue be related to #9651? If not, maybe start a new Discussion? (to avoid everyone in this PR being notified of any comments 😄 )

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FR: Events - pass in old data & and is_dirty boolean

7 participants

@ryanmitchell@jasonvarga@duncanmcclean@jonassiewertsen@edalzell@afonic@jackmcdade