From 57886119625edce78802173b3c258e9559e744c3 Mon Sep 17 00:00:00 2001 From: David Wertheimer Date: Thu, 9 Nov 2023 17:15:42 -0800 Subject: [PATCH] TaskAutomations and Plugin Info updates --- .../__tests__/NPTaskScanAndProcess.test.js | 46 +++++++++++++++++ dwertheimer.TaskAutomations/changelog.md | 4 +- dwertheimer.TaskAutomations/plugin.json | 4 +- .../src/NPTaskScanAndProcess.js | 44 ++++++++++++++++- np.Shared/requiredFiles/react.c.Root.dev.js | 49 +++++++++++++------ np.Shared/requiredFiles/react.c.Root.min.js | 2 +- np.plugin-test/plugin.json | 2 +- np.plugin-test/requiredFiles/css.plugin.css | 40 ++++++++++++++- 8 files changed, 168 insertions(+), 23 deletions(-) diff --git a/dwertheimer.TaskAutomations/__tests__/NPTaskScanAndProcess.test.js b/dwertheimer.TaskAutomations/__tests__/NPTaskScanAndProcess.test.js index 49e21febe..71028ef9b 100644 --- a/dwertheimer.TaskAutomations/__tests__/NPTaskScanAndProcess.test.js +++ b/dwertheimer.TaskAutomations/__tests__/NPTaskScanAndProcess.test.js @@ -41,6 +41,52 @@ describe(`${PLUGIN_NAME}`, () => { const result = await f.prepareUserAction(origPara, {}, '__remove__') expect(result).toEqual({ action: 'set', changed: changedPara }) }) + test('should set a p1 to !', async () => { + const origPara = { content: `foo` } + const changedPara = { content: `! foo` } + const result = await f.prepareUserAction(origPara, {}, '__p1__') + expect(result).toEqual({ action: '__p1__', changed: changedPara }) + }) + }) + /* + * updatePriority() + */ + describe('updatePriority()' /* function */, () => { + test('should remove the priority', () => { + const before = { content: `foo ! bar` } + const result = f.updatePriority(before, 'p0') + expect(result.content).toEqual(`foo bar`) + }) + test('should update p1 of task with no priority', () => { + const before = { content: `foo bar` } + const result = f.updatePriority(before, 'p1') + expect(result.content).toEqual(`! foo bar`) + }) + test('should update p2 of task with no priority', () => { + const before = { content: `foo bar` } + const result = f.updatePriority(before, 'p2') + expect(result.content).toEqual(`!! foo bar`) + }) + test('should update p3 of task with no priority', () => { + const before = { content: `foo bar` } + const result = f.updatePriority(before, 'p3') + expect(result.content).toEqual(`!!! foo bar`) + }) + test('should update p3 of task with prev prio at beginning', () => { + const before = { content: `!! foo bar` } + const result = f.updatePriority(before, 'p3') + expect(result.content).toEqual(`!!! foo bar`) + }) + test('should update priority of task with prev prio in middle', () => { + const before = { content: `foo !! bar` } + const result = f.updatePriority(before, 'p3') + expect(result.content).toEqual(`!!! foo bar`) + }) + test('should update priority of task with prev prio at end', () => { + const before = { content: `foo bar !` } + const result = f.updatePriority(before, 'p3') + expect(result.content).toEqual(`!!! foo bar`) + }) }) }) }) diff --git a/dwertheimer.TaskAutomations/changelog.md b/dwertheimer.TaskAutomations/changelog.md index 1dee196ac..2a8c79ff0 100644 --- a/dwertheimer.TaskAutomations/changelog.md +++ b/dwertheimer.TaskAutomations/changelog.md @@ -7,8 +7,8 @@ ## [2.17.0] @dwertheimer 2023-08-29 -- change edit to allow for -- opt-click a date return you to edit +- Overdue processing: change edit to allow you to opt-click a date return you to edit +- Overdue processing: added p1,p2,p3 options. Thx @george! ## [2.16.0] @dwertheimer 2023-08-23 diff --git a/dwertheimer.TaskAutomations/plugin.json b/dwertheimer.TaskAutomations/plugin.json index 5af6e9a72..ff5c1d5c9 100644 --- a/dwertheimer.TaskAutomations/plugin.json +++ b/dwertheimer.TaskAutomations/plugin.json @@ -5,8 +5,8 @@ "plugin.name": "✅ Task Automations", "plugin.description": "Automations for handling Tasks:\n- Overdue/Forgotten task scanning\n- Task sorting within a note\n- Copying #tags/@mentions from one task to another\n- Mark all tasks in note open/completed\n- Automatically opening URLs of task lines", "plugin.author": "@dwertheimer", - "plugin.version": "2.16.99-notReleasedYet", - "plugin.lastUpdateInfo": "2.16.0 Overdue processing: Add move-to-note as list/checklist option", + "plugin.version": "2.17.0", + "plugin.lastUpdateInfo": "2.17.0: Overdue processing: added p1,p2,p3 options. Thx @george!", "plugin.dependencies": [], "plugin.requiredFiles": [ "css.plugin.css", diff --git a/dwertheimer.TaskAutomations/src/NPTaskScanAndProcess.js b/dwertheimer.TaskAutomations/src/NPTaskScanAndProcess.js index 10192571b..429fc489a 100644 --- a/dwertheimer.TaskAutomations/src/NPTaskScanAndProcess.js +++ b/dwertheimer.TaskAutomations/src/NPTaskScanAndProcess.js @@ -45,6 +45,10 @@ type RescheduleUserAction = | '__mdfuture__' | '__newTask__' | '__opentask__' + | '__p0__' + | '__p1__' + | '__p2__' + | '__p3__' | string /* >dateOptions */ | number /* lineIndex of item to pop open */ @@ -118,7 +122,7 @@ export function getSharedOptions(origPara?: TParagraph | { note: TNote } | null, } /** - * Open a note, highlight the task being looked at and prompt user for a choice of what to do with one specific line + * Open a note, highlight the task being looked at and prompt user for a choice of what to do with one specific line/task * @param {*} origPara * @returns {Promise} the user choice or false */ @@ -137,6 +141,10 @@ async function promptUserToActOnLine(origPara: TParagraph /*, updatedPara: TPara { label: `✓⏎ Mark done and add follow-up in same note`, value: '__mdhere__' }, { label: `✓📆 Mark done and add follow-up in future note`, value: '__mdfuture__' }, { label: `💡 This reminds me...(create new task then continue)`, value: '__newTask__' }, + { label: `x! Remove priority from task`, value: '__p0__' }, + { label: `! Set priority to p1`, value: '__p1__' }, + { label: `!! Set priority to p2`, value: '__p2__' }, + { label: `!!! Set priority to p3`, value: '__p3__' }, ...sharedOpts, { label: `␡ Delete this line (be sure!)`, value: '__delete__' }, ] @@ -151,6 +159,20 @@ async function promptUserToActOnLine(origPara: TParagraph /*, updatedPara: TPara return res } +/** + * Update a task's priority no matter where it was before + * @param {*} input + * @param {*} newPriority + * @returns + */ +export function updatePriority(input: TParagraph, newPriority: string): TParagraph { + const prioStr = newPriority === 'p1' ? '!' : newPriority === 'p2' ? '!!' : newPriority === 'p3' ? '!!!' : '' + const output = input + output.content = output.content.replace(/!\s*/g, '').replace(/\s+!/g, '') + output.content = `${prioStr ? `${prioStr} ` : ''}${output.content}`.trim() + return output +} + /** * Given a user choice on a specific action to take on a line, create an {action: string, changed?: TParagraph, userChoice?: string} object for further processing * @param {TParagraph} origPara @@ -207,6 +229,13 @@ export async function prepareUserAction(origPara: TParagraph, updatedPara: TPara case `__mdfuture__`: { return { action: userChoice, changed: origPara } } + case '__p0__': + case '__p1__': + case '__p2__': + case '__p3__': { + logDebug(`prepareUserAction: ${userChoice}`) + return { action: userChoice, changed: updatePriority(origPara, userChoice.replace(/_/g, '')) } + } case `__today__`: { origPara.content = replaceArrowDatesInString(origPara.content, '>today') return { action: 'set', changed: origPara } // dbw NOTE: this said "updatedPara". Not sure how/why that worked before. changing it for React @@ -224,6 +253,7 @@ export async function prepareUserAction(origPara: TParagraph, updatedPara: TPara case '__skip__': return { action: 'skip', changed: origPara } } + if (typeof userChoice === 'string' && userChoice[0] === '>') { origPara.content = replaceArrowDatesInString(origPara.content, userChoice) return { action: 'set', changed: origPara, userChoice } @@ -394,6 +424,18 @@ async function reviewNote(notesToUpdate: Array>, noteIndex: nu await appendTaskToCalendarNote(getTodaysDateHyphenated()) return updates.length ? noteIndex - 1 : noteIndex } + case '__p0__': + case '__p1__': + case '__p2__': + case '__p3__': { + logDebug(`reviewNote: index:${index}, updates.length=${updates.length} currentTaskIndex=${currentTaskIndex}`) + clo(updates, `reviewNote: updates:`) + if (result?.changed) { + updates[index] = result.changed + note.updateParagraph(updates[index]) + return noteIndex - 1 + } + } } //user selected an item in the list to come back to later (in splitview) // const range = note.paragraphs[Number(res) || 0].contentRange diff --git a/np.Shared/requiredFiles/react.c.Root.dev.js b/np.Shared/requiredFiles/react.c.Root.dev.js index 5a25ea04d..da1f279ce 100644 --- a/np.Shared/requiredFiles/react.c.Root.dev.js +++ b/np.Shared/requiredFiles/react.c.Root.dev.js @@ -236,7 +236,7 @@ var RootBundle = (function (exports, React$1) { // color this component's output differently in the console const consoleStyle = 'background: #222; color: #62AFEC'; const logDebug = (msg, ...args) => console.log(`${window.webkit ? '' : '%c'}${msg}`, consoleStyle, ...args); - const logSubtle = (msg, ...args) => console.log(`%c${msg}`, 'color: #6D6962', ...args); + const logSubtle = (msg, ...args) => console.log(`${window.webkit ? '' : '%c'}${msg}`, 'color: #6D6962', ...args); // used by the ErrorBoundary component const myErrorLogger = (error, info) => { @@ -323,18 +323,35 @@ var RootBundle = (function (exports, React$1) { }); // dispatch the message to the reducer }; + /** + * Ignore messages that have nothing to do with the plugin + * @param {Event} event + * @returns {boolean} + */ + const shouldIgnoreMessage = event => { + const { + origin, + source, + data + } = event; + // logDebug( + // `Root: shouldIgnoreMessage origin=${origin} source=${source} data=${JSON.stringify(data)} data.source=${ + // data?.source + // } /react-devtools/.test(data?.source=${/react-devtools/.test(data?.source)}}`, + // ) + return typeof data === 'string' && data?.startsWith('setImmediate$') || typeof data === 'object' && data?.hasOwnProperty('iframeSrc') || /react-devtools/.test(data?.source); + }; + /** * This is effectively a reducer we will use to process messages from the plugin * And also from components down the tree, using the dispatch command */ const onMessageReceived = event => { const { - origin, - source, data } = event; - if (data && !(typeof data === 'string' && data.startsWith('setImmediate$')) && !data.iframeSrc) { - JSON.stringify(event, null, 4); + if (!shouldIgnoreMessage(event) && data) { + // const str = JSON.stringify(event, null, 4) try { // $FlowFixMe const { @@ -451,7 +468,7 @@ var RootBundle = (function (exports, React$1) { // send some info to the plugin // first param is the action type and the rest are data (can be any form you want) // data.foo = 'bar' - sendMessageToPlugin(['commsBridgeTest', 'drink green', 'tea']); + sendMessageToPlugin(['commsBridgeTest', 'some sample', 'data passed']); }; /** @@ -516,12 +533,8 @@ var RootBundle = (function (exports, React$1) { data: npData, dispatch: dispatch }), (debug) && /*#__PURE__*/React__default["default"].createElement(React__default["default"].StrictMode, null, /*#__PURE__*/React__default["default"].createElement("div", { - onClick: () => dispatch('SHOW_BANNER', { - msg: 'Banner test succeeded' - }, `banner test`) - }, "Local Banner Display Test"), /*#__PURE__*/React__default["default"].createElement("div", { - onClick: testCommsBridge - }, "Test Communication Bridge"), /*#__PURE__*/React__default["default"].createElement("div", null, /*#__PURE__*/React__default["default"].createElement("span", { + className: "w3-container w3-green" + }, "Debugging information (Plugin passed debug variable = true)"), /*#__PURE__*/React__default["default"].createElement("div", null, /*#__PURE__*/React__default["default"].createElement("span", { id: "debugHistory" }, "History (most recent first):"), /*#__PURE__*/React__default["default"].createElement("ul", null, history.slice().reverse().map((h, i) => /*#__PURE__*/React__default["default"].createElement("li", { style: { @@ -530,9 +543,15 @@ var RootBundle = (function (exports, React$1) { key: i }, "[", h?.date || '', "]: ", h?.msg || ''))), /*#__PURE__*/React__default["default"].createElement("div", { className: "monospaceData" - }, "overdue paras: ", JSON.stringify(globalSharedData.overdueParas, null, 2)), /*#__PURE__*/React__default["default"].createElement("div", { - className: "monospaceData" - }, "globalSharedData: ", JSON.stringify(globalSharedData, null, 2)))))); + }, "globalSharedData: ", JSON.stringify(globalSharedData, null, 2))), /*#__PURE__*/React__default["default"].createElement("div", { + className: "w3-button w3-black", + onClick: () => dispatch('SHOW_BANNER', { + msg: 'Banner test succeeded' + }, `banner test`) + }, "Local Banner Display Test"), /*#__PURE__*/React__default["default"].createElement("div", { + className: "w3-button w3-black", + onClick: testCommsBridge + }, "Test Communication Bridge")))); } exports.Root = Root; diff --git a/np.Shared/requiredFiles/react.c.Root.min.js b/np.Shared/requiredFiles/react.c.Root.min.js index f2ada74d7..0ddaaa424 100644 --- a/np.Shared/requiredFiles/react.c.Root.min.js +++ b/np.Shared/requiredFiles/react.c.Root.min.js @@ -1 +1 @@ -var RootBundle=function(exports,React$1){"use strict";function _interopDefaultLegacy(e){return e&&typeof e==="object"&&"default"in e?e:{default:e}}var React__default=_interopDefaultLegacy(React$1);var commonjsGlobal=typeof globalThis!=="undefined"?globalThis:typeof window!=="undefined"?window:typeof global!=="undefined"?global:typeof self!=="undefined"?self:{};var reactErrorBoundary_umd={exports:{}};(function(module,exports){(function(global,factory){factory(exports,React__default["default"])})(commonjsGlobal,(function(exports,React){function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach((function(k){if(k!=="default"){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}})}}))}n["default"]=e;return Object.freeze(n)}var React__namespace=_interopNamespace(React);function _setPrototypeOf(o,p){_setPrototypeOf=Object.setPrototypeOf||function _setPrototypeOf(o,p){o.__proto__=p;return o};return _setPrototypeOf(o,p)}function _inheritsLoose(subClass,superClass){subClass.prototype=Object.create(superClass.prototype);subClass.prototype.constructor=subClass;_setPrototypeOf(subClass,superClass)}var changedArray=function changedArray(a,b){if(a===void 0){a=[]}if(b===void 0){b=[]}return a.length!==b.length||a.some((function(item,index){return!Object.is(item,b[index])}))};var initialState={error:null};var ErrorBoundary=function(_React$Component){_inheritsLoose(ErrorBoundary,_React$Component);function ErrorBoundary(){var _this;for(var _len=arguments.length,_args=new Array(_len),_key=0;_key<_len;_key++){_args[_key]=arguments[_key]}_this=_React$Component.call.apply(_React$Component,[this].concat(_args))||this;_this.state=initialState;_this.resetErrorBoundary=function(){var _this$props;for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++){args[_key2]=arguments[_key2]}_this.props.onReset==null?void 0:(_this$props=_this.props).onReset.apply(_this$props,args);_this.reset()};return _this}ErrorBoundary.getDerivedStateFromError=function getDerivedStateFromError(error){return{error:error}};var _proto=ErrorBoundary.prototype;_proto.reset=function reset(){this.setState(initialState)};_proto.componentDidCatch=function componentDidCatch(error,info){var _this$props$onError,_this$props2;(_this$props$onError=(_this$props2=this.props).onError)==null?void 0:_this$props$onError.call(_this$props2,error,info)};_proto.componentDidUpdate=function componentDidUpdate(prevProps,prevState){var error=this.state.error;var resetKeys=this.props.resetKeys;if(error!==null&&prevState.error!==null&&changedArray(prevProps.resetKeys,resetKeys)){var _this$props$onResetKe,_this$props3;(_this$props$onResetKe=(_this$props3=this.props).onResetKeysChange)==null?void 0:_this$props$onResetKe.call(_this$props3,prevProps.resetKeys,resetKeys);this.reset()}};_proto.render=function render(){var error=this.state.error;var _this$props4=this.props,fallbackRender=_this$props4.fallbackRender,FallbackComponent=_this$props4.FallbackComponent,fallback=_this$props4.fallback;if(error!==null){var _props={error:error,resetErrorBoundary:this.resetErrorBoundary};if(React__namespace.isValidElement(fallback)){return fallback}else if(typeof fallbackRender==="function"){return fallbackRender(_props)}else if(FallbackComponent){return React__namespace.createElement(FallbackComponent,_props)}else{throw new Error("react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop")}}return this.props.children};return ErrorBoundary}(React__namespace.Component);function withErrorBoundary(Component,errorBoundaryProps){var Wrapped=function Wrapped(props){return React__namespace.createElement(ErrorBoundary,errorBoundaryProps,React__namespace.createElement(Component,props))};var name=Component.displayName||Component.name||"Unknown";Wrapped.displayName="withErrorBoundary("+name+")";return Wrapped}function useErrorHandler(givenError){var _React$useState=React__namespace.useState(null),error=_React$useState[0],setError=_React$useState[1];if(givenError!=null)throw givenError;if(error!=null)throw error;return setError}exports.ErrorBoundary=ErrorBoundary;exports.useErrorHandler=useErrorHandler;exports.withErrorBoundary=withErrorBoundary;Object.defineProperty(exports,"__esModule",{value:true})}))})(reactErrorBoundary_umd,reactErrorBoundary_umd.exports);function MessageBanner(props){if(!props.warn){return null}console.log(`Root: MessageBanner: props=${JSON.stringify(props)}`);const className=`w3-panel w3-display-container ${props.border?"w3-leftbar":""} ${props.border??"w3-border-red"} ${props.color??"w3-pale-red"}`;window.scrollTo(0,0);return React.createElement("div",{className:className},React.createElement("span",{onClick:()=>props.hide(),className:"w3-button w3-display-right"},"X"),React.createElement("p",null,props.msg))}const ErrorFallback=({error:error,resetErrorBoundary:resetErrorBoundary})=>React.createElement("div",{role:"alert"},React.createElement("h1",null,"Something went wrong in React:"),React.createElement("pre",null,error.message),React.createElement("button",{onClick:resetErrorBoundary},"Try again"));const consoleStyle="background: #222; color: #62AFEC";const logDebug=(msg,...args)=>console.log(`${window.webkit?"":"%c"}${msg}`,consoleStyle,...args);const logSubtle=(msg,...args)=>console.log(`%c${msg}`,"color: #6D6962",...args);const myErrorLogger=(error,info)=>{console.log(`%cRoot: ErrorBoundary got error: error=\n${JSON.stringify(error)},\ninfo=${JSON.stringify(info)}`,"background: #ff0000; color: #ffffff")};const{lastUpdated:lastUpdated=null,returnPluginCommand:returnPluginCommand={},debug:debug=false,ENV_MODE:ENV_MODE}=globalSharedData;if(typeof globalSharedData==="undefined"||!globalSharedData)logDebug("Root: Root: globalSharedData is undefined",globalSharedData);if(typeof globalSharedData==="undefined")throw globalSharedData;if(typeof globalSharedData.lastUpdated==="undefined")throw`Root: globalSharedData.lastUpdated is undefined`;function Root(props){const[npData,setNPData]=React__default["default"].useState(globalSharedData);const[warning,setWarning]=React__default["default"].useState({warn:false,msg:"",color:"w3-pale-red"});const[messageFromPlugin,setMessageFromPlugin]=React__default["default"].useState({});const[history,setHistory]=React__default["default"].useState([lastUpdated]);const tempSavedClicksRef=React__default["default"].useRef([]);const MemoizedWebView=React__default["default"].memo(WebView);debug&&logDebug(`Root: Running in Debug mode. Note: is enabled which will run effects twice each time they are rendered. This is to help find bugs in your code.`);const onClickCapture=e=>{if(!debug)return;logDebug(`Root: onClickCapture: ${e.target.tagName} ${e.target.className}`);logDebug(`Root: onClickCapture ${e.type} ${e.target.outerText} e=`,e);tempSavedClicksRef.current.push({date:(new Date).toLocaleDateString(),msg:`UI_CLICK ${e.type} ${e.target.outerText}`})};const dispatch=(action,data,actionDescriptionForLog="")=>{const desc=`${action}${actionDescriptionForLog?`: ${actionDescriptionForLog}`:""}`;data.lastUpdated={msg:desc,date:(new Date).toLocaleString()};onMessageReceived({data:{type:action,payload:data}})};const onMessageReceived=event=>{const{origin:origin,source:source,data:data}=event;if(data&&!(typeof data==="string"&&data.startsWith("setImmediate$"))&&!data.iframeSrc){JSON.stringify(event,null,4);try{const{type:type,payload:payload}=event.data;if(!type)throw`onMessageReceived: event.data.type is undefined`,event.data;if(!payload)throw`onMessageReceived: event.data.payload is undefined`,event.data;if(type&&payload){logDebug(`Root: onMessageReceived: ${type}`);if(type==="SHOW_BANNER")payload.lastUpdated.msg+=`: ${payload.msg}`;setHistory((prevData=>[...prevData,...tempSavedClicksRef.current,payload.lastUpdated]));tempSavedClicksRef.current=[];switch(type){case"SET_TITLE":document.title=payload.title;break;case"SET_DATA":case"UPDATE_DATA":setNPData((prevData=>({...prevData,...payload})));globalSharedData={...globalSharedData,...payload};logDebug("Root: SET_DATA after setting globalSharedData=",globalSharedData);break;case"SHOW_BANNER":showBanner(payload.msg,payload.color,payload.border);break;case"SEND_TO_PLUGIN":sendToPlugin(payload);break;case"RETURN_VALUE":logDebug(`Root: onMessageReceived: processing payload`);setMessageFromPlugin(payload);break;default:break}}else{logDebug(`Root: onMessageReceived: called but event.data.type and/or event.data.payload is undefined`,event)}}catch(error){logDebug("Root: onMessageReceived: error="+JSON.stringify(error)+"error="+JSON.stringify(error))}}};const sendToPlugin=React__default["default"].useCallback((args=>{const returnPluginCommand=globalSharedData.returnPluginCommand||"undefined";if(returnPluginCommand==="undefined"||!returnPluginCommand?.command||!returnPluginCommand?.id)throw"returnPluginCommand variable is not passed correctly to set up comms bridge. Check your data object which you are sending to invoke React";if(!returnPluginCommand?.command)throw"returnPluginCommand.cmd is not defined in the intial data passed to the plugin";if(!returnPluginCommand?.id)throw"returnPluginCommand.id is not defined in the intial data passed to the plugin";const{command:command,id:id}=returnPluginCommand;runPluginCommand(command,id,args)}),[]);const showBanner=(msg,color="w3-pale-red",border="w3-border-red")=>{const warnObj={warn:true,msg:msg,color:color,border:border};logDebug(`Root: showBanner: sending: ${JSON.stringify(warnObj)}`);setWarning(warnObj)};const hideBanner=()=>{setWarning({warn:false,msg:"",color:"w3-pale-red"})};const testCommsBridge=()=>{logDebug(`Root: _Root: testCommsBridge`);sendMessageToPlugin(["commsBridgeTest","drink green","tea"])};function onRender(id,phase,actualDuration,baseDuration,startTime,commitTime,interactions){logSubtle(`\n===================\nPROFILING:${id} phase=${phase} actualDuration=${actualDuration} baseDuration=${baseDuration} startTime=${startTime} commitTime=${commitTime}\n===================\n`)}React$1.useEffect((()=>{window.addEventListener("message",onMessageReceived);return()=>window.removeEventListener("message",onMessageReceived)}),[]);return React__default["default"].createElement(reactErrorBoundary_umd.exports.ErrorBoundary,{FallbackComponent:ErrorFallback,onReset:()=>{},onError:myErrorLogger},React__default["default"].createElement("div",{className:"Root",onClickCapture:onClickCapture},React__default["default"].createElement(MessageBanner,{warn:warning.warn,msg:warning.msg,color:warning.color,border:warning.border,hide:hideBanner}),debug?React__default["default"].createElement(React$1.Profiler,{id:"MemoizedWebView",onRender:onRender},React__default["default"].createElement(MemoizedWebView,{dispatch:dispatch,data:npData})):React__default["default"].createElement(MemoizedWebView,{data:npData,dispatch:dispatch}),debug&&React__default["default"].createElement(React__default["default"].StrictMode,null,React__default["default"].createElement("div",{onClick:()=>dispatch("SHOW_BANNER",{msg:"Banner test succeeded"},`banner test`)},"Local Banner Display Test"),React__default["default"].createElement("div",{onClick:testCommsBridge},"Test Communication Bridge"),React__default["default"].createElement("div",null,React__default["default"].createElement("span",{id:"debugHistory"},"History (most recent first):"),React__default["default"].createElement("ul",null,history.slice().reverse().map(((h,i)=>React__default["default"].createElement("li",{style:{fontSize:"12px"},key:i},"[",h?.date||"","]: ",h?.msg||"")))),React__default["default"].createElement("div",{className:"monospaceData"},"overdue paras: ",JSON.stringify(globalSharedData.overdueParas,null,2)),React__default["default"].createElement("div",{className:"monospaceData"},"globalSharedData: ",JSON.stringify(globalSharedData,null,2))))))}exports.Root=Root;Object.defineProperty(exports,"__esModule",{value:true});return exports}({},react);Object.assign(typeof globalThis=="undefined"?this:globalThis,RootBundle); +var RootBundle=function(exports,React$1){"use strict";function _interopDefaultLegacy(e){return e&&typeof e==="object"&&"default"in e?e:{default:e}}var React__default=_interopDefaultLegacy(React$1);var commonjsGlobal=typeof globalThis!=="undefined"?globalThis:typeof window!=="undefined"?window:typeof global!=="undefined"?global:typeof self!=="undefined"?self:{};var reactErrorBoundary_umd={exports:{}};(function(module,exports){(function(global,factory){factory(exports,React__default["default"])})(commonjsGlobal,(function(exports,React){function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach((function(k){if(k!=="default"){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}})}}))}n["default"]=e;return Object.freeze(n)}var React__namespace=_interopNamespace(React);function _setPrototypeOf(o,p){_setPrototypeOf=Object.setPrototypeOf||function _setPrototypeOf(o,p){o.__proto__=p;return o};return _setPrototypeOf(o,p)}function _inheritsLoose(subClass,superClass){subClass.prototype=Object.create(superClass.prototype);subClass.prototype.constructor=subClass;_setPrototypeOf(subClass,superClass)}var changedArray=function changedArray(a,b){if(a===void 0){a=[]}if(b===void 0){b=[]}return a.length!==b.length||a.some((function(item,index){return!Object.is(item,b[index])}))};var initialState={error:null};var ErrorBoundary=function(_React$Component){_inheritsLoose(ErrorBoundary,_React$Component);function ErrorBoundary(){var _this;for(var _len=arguments.length,_args=new Array(_len),_key=0;_key<_len;_key++){_args[_key]=arguments[_key]}_this=_React$Component.call.apply(_React$Component,[this].concat(_args))||this;_this.state=initialState;_this.resetErrorBoundary=function(){var _this$props;for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++){args[_key2]=arguments[_key2]}_this.props.onReset==null?void 0:(_this$props=_this.props).onReset.apply(_this$props,args);_this.reset()};return _this}ErrorBoundary.getDerivedStateFromError=function getDerivedStateFromError(error){return{error:error}};var _proto=ErrorBoundary.prototype;_proto.reset=function reset(){this.setState(initialState)};_proto.componentDidCatch=function componentDidCatch(error,info){var _this$props$onError,_this$props2;(_this$props$onError=(_this$props2=this.props).onError)==null?void 0:_this$props$onError.call(_this$props2,error,info)};_proto.componentDidUpdate=function componentDidUpdate(prevProps,prevState){var error=this.state.error;var resetKeys=this.props.resetKeys;if(error!==null&&prevState.error!==null&&changedArray(prevProps.resetKeys,resetKeys)){var _this$props$onResetKe,_this$props3;(_this$props$onResetKe=(_this$props3=this.props).onResetKeysChange)==null?void 0:_this$props$onResetKe.call(_this$props3,prevProps.resetKeys,resetKeys);this.reset()}};_proto.render=function render(){var error=this.state.error;var _this$props4=this.props,fallbackRender=_this$props4.fallbackRender,FallbackComponent=_this$props4.FallbackComponent,fallback=_this$props4.fallback;if(error!==null){var _props={error:error,resetErrorBoundary:this.resetErrorBoundary};if(React__namespace.isValidElement(fallback)){return fallback}else if(typeof fallbackRender==="function"){return fallbackRender(_props)}else if(FallbackComponent){return React__namespace.createElement(FallbackComponent,_props)}else{throw new Error("react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop")}}return this.props.children};return ErrorBoundary}(React__namespace.Component);function withErrorBoundary(Component,errorBoundaryProps){var Wrapped=function Wrapped(props){return React__namespace.createElement(ErrorBoundary,errorBoundaryProps,React__namespace.createElement(Component,props))};var name=Component.displayName||Component.name||"Unknown";Wrapped.displayName="withErrorBoundary("+name+")";return Wrapped}function useErrorHandler(givenError){var _React$useState=React__namespace.useState(null),error=_React$useState[0],setError=_React$useState[1];if(givenError!=null)throw givenError;if(error!=null)throw error;return setError}exports.ErrorBoundary=ErrorBoundary;exports.useErrorHandler=useErrorHandler;exports.withErrorBoundary=withErrorBoundary;Object.defineProperty(exports,"__esModule",{value:true})}))})(reactErrorBoundary_umd,reactErrorBoundary_umd.exports);function MessageBanner(props){if(!props.warn){return null}console.log(`Root: MessageBanner: props=${JSON.stringify(props)}`);const className=`w3-panel w3-display-container ${props.border?"w3-leftbar":""} ${props.border??"w3-border-red"} ${props.color??"w3-pale-red"}`;window.scrollTo(0,0);return React.createElement("div",{className:className},React.createElement("span",{onClick:()=>props.hide(),className:"w3-button w3-display-right"},"X"),React.createElement("p",null,props.msg))}const ErrorFallback=({error:error,resetErrorBoundary:resetErrorBoundary})=>React.createElement("div",{role:"alert"},React.createElement("h1",null,"Something went wrong in React:"),React.createElement("pre",null,error.message),React.createElement("button",{onClick:resetErrorBoundary},"Try again"));const consoleStyle="background: #222; color: #62AFEC";const logDebug=(msg,...args)=>console.log(`${window.webkit?"":"%c"}${msg}`,consoleStyle,...args);const logSubtle=(msg,...args)=>console.log(`${window.webkit?"":"%c"}${msg}`,"color: #6D6962",...args);const myErrorLogger=(error,info)=>{console.log(`%cRoot: ErrorBoundary got error: error=\n${JSON.stringify(error)},\ninfo=${JSON.stringify(info)}`,"background: #ff0000; color: #ffffff")};const{lastUpdated:lastUpdated=null,returnPluginCommand:returnPluginCommand={},debug:debug=false,ENV_MODE:ENV_MODE}=globalSharedData;if(typeof globalSharedData==="undefined"||!globalSharedData)logDebug("Root: Root: globalSharedData is undefined",globalSharedData);if(typeof globalSharedData==="undefined")throw globalSharedData;if(typeof globalSharedData.lastUpdated==="undefined")throw`Root: globalSharedData.lastUpdated is undefined`;function Root(props){const[npData,setNPData]=React__default["default"].useState(globalSharedData);const[warning,setWarning]=React__default["default"].useState({warn:false,msg:"",color:"w3-pale-red"});const[messageFromPlugin,setMessageFromPlugin]=React__default["default"].useState({});const[history,setHistory]=React__default["default"].useState([lastUpdated]);const tempSavedClicksRef=React__default["default"].useRef([]);const MemoizedWebView=React__default["default"].memo(WebView);debug&&logDebug(`Root: Running in Debug mode. Note: is enabled which will run effects twice each time they are rendered. This is to help find bugs in your code.`);const onClickCapture=e=>{if(!debug)return;logDebug(`Root: onClickCapture: ${e.target.tagName} ${e.target.className}`);logDebug(`Root: onClickCapture ${e.type} ${e.target.outerText} e=`,e);tempSavedClicksRef.current.push({date:(new Date).toLocaleDateString(),msg:`UI_CLICK ${e.type} ${e.target.outerText}`})};const dispatch=(action,data,actionDescriptionForLog="")=>{const desc=`${action}${actionDescriptionForLog?`: ${actionDescriptionForLog}`:""}`;data.lastUpdated={msg:desc,date:(new Date).toLocaleString()};onMessageReceived({data:{type:action,payload:data}})};const shouldIgnoreMessage=event=>{const{origin:origin,source:source,data:data}=event;return typeof data==="string"&&data?.startsWith("setImmediate$")||typeof data==="object"&&data?.hasOwnProperty("iframeSrc")||/react-devtools/.test(data?.source)};const onMessageReceived=event=>{const{data:data}=event;if(!shouldIgnoreMessage(event)&&data){try{const{type:type,payload:payload}=event.data;if(!type)throw`onMessageReceived: event.data.type is undefined`,event.data;if(!payload)throw`onMessageReceived: event.data.payload is undefined`,event.data;if(type&&payload){logDebug(`Root: onMessageReceived: ${type}`);if(type==="SHOW_BANNER")payload.lastUpdated.msg+=`: ${payload.msg}`;setHistory((prevData=>[...prevData,...tempSavedClicksRef.current,payload.lastUpdated]));tempSavedClicksRef.current=[];switch(type){case"SET_TITLE":document.title=payload.title;break;case"SET_DATA":case"UPDATE_DATA":setNPData((prevData=>({...prevData,...payload})));globalSharedData={...globalSharedData,...payload};logDebug("Root: SET_DATA after setting globalSharedData=",globalSharedData);break;case"SHOW_BANNER":showBanner(payload.msg,payload.color,payload.border);break;case"SEND_TO_PLUGIN":sendToPlugin(payload);break;case"RETURN_VALUE":logDebug(`Root: onMessageReceived: processing payload`);setMessageFromPlugin(payload);break;default:break}}else{logDebug(`Root: onMessageReceived: called but event.data.type and/or event.data.payload is undefined`,event)}}catch(error){logDebug("Root: onMessageReceived: error="+JSON.stringify(error)+"error="+JSON.stringify(error))}}};const sendToPlugin=React__default["default"].useCallback((args=>{const returnPluginCommand=globalSharedData.returnPluginCommand||"undefined";if(returnPluginCommand==="undefined"||!returnPluginCommand?.command||!returnPluginCommand?.id)throw"returnPluginCommand variable is not passed correctly to set up comms bridge. Check your data object which you are sending to invoke React";if(!returnPluginCommand?.command)throw"returnPluginCommand.cmd is not defined in the intial data passed to the plugin";if(!returnPluginCommand?.id)throw"returnPluginCommand.id is not defined in the intial data passed to the plugin";const{command:command,id:id}=returnPluginCommand;runPluginCommand(command,id,args)}),[]);const showBanner=(msg,color="w3-pale-red",border="w3-border-red")=>{const warnObj={warn:true,msg:msg,color:color,border:border};logDebug(`Root: showBanner: sending: ${JSON.stringify(warnObj)}`);setWarning(warnObj)};const hideBanner=()=>{setWarning({warn:false,msg:"",color:"w3-pale-red"})};const testCommsBridge=()=>{logDebug(`Root: _Root: testCommsBridge`);sendMessageToPlugin(["commsBridgeTest","some sample","data passed"])};function onRender(id,phase,actualDuration,baseDuration,startTime,commitTime,interactions){logSubtle(`\n===================\nPROFILING:${id} phase=${phase} actualDuration=${actualDuration} baseDuration=${baseDuration} startTime=${startTime} commitTime=${commitTime}\n===================\n`)}React$1.useEffect((()=>{window.addEventListener("message",onMessageReceived);return()=>window.removeEventListener("message",onMessageReceived)}),[]);return React__default["default"].createElement(reactErrorBoundary_umd.exports.ErrorBoundary,{FallbackComponent:ErrorFallback,onReset:()=>{},onError:myErrorLogger},React__default["default"].createElement("div",{className:"Root",onClickCapture:onClickCapture},React__default["default"].createElement(MessageBanner,{warn:warning.warn,msg:warning.msg,color:warning.color,border:warning.border,hide:hideBanner}),debug?React__default["default"].createElement(React$1.Profiler,{id:"MemoizedWebView",onRender:onRender},React__default["default"].createElement(MemoizedWebView,{dispatch:dispatch,data:npData})):React__default["default"].createElement(MemoizedWebView,{data:npData,dispatch:dispatch}),debug&&React__default["default"].createElement(React__default["default"].StrictMode,null,React__default["default"].createElement("div",{className:"w3-container w3-green"},"Debugging information (Plugin passed debug variable = true)"),React__default["default"].createElement("div",null,React__default["default"].createElement("span",{id:"debugHistory"},"History (most recent first):"),React__default["default"].createElement("ul",null,history.slice().reverse().map(((h,i)=>React__default["default"].createElement("li",{style:{fontSize:"12px"},key:i},"[",h?.date||"","]: ",h?.msg||"")))),React__default["default"].createElement("div",{className:"monospaceData"},"globalSharedData: ",JSON.stringify(globalSharedData,null,2))),React__default["default"].createElement("div",{className:"w3-button w3-black",onClick:()=>dispatch("SHOW_BANNER",{msg:"Banner test succeeded"},`banner test`)},"Local Banner Display Test"),React__default["default"].createElement("div",{className:"w3-button w3-black",onClick:testCommsBridge},"Test Communication Bridge"))))}exports.Root=Root;Object.defineProperty(exports,"__esModule",{value:true});return exports}({},react);Object.assign(typeof globalThis=="undefined"?this:globalThis,RootBundle); diff --git a/np.plugin-test/plugin.json b/np.plugin-test/plugin.json index 170dd56c6..349d5d6c1 100644 --- a/np.plugin-test/plugin.json +++ b/np.plugin-test/plugin.json @@ -6,7 +6,7 @@ "plugin.name": "🔌 Plugin Information & Tester", "plugin.description": "View Plugin Commands and Test that Plugins are working.", "plugin.author": "@dwertheimer", - "plugin.version": "1.4.0-beta6", + "plugin.version": "1.4.0-beta9", "plugin.lastUpdateInfo": "1.4.0-betaX: The beginning of a searchable plugin repository", "plugin.dependencies": [], "plugin.requiredFiles": [ diff --git a/np.plugin-test/requiredFiles/css.plugin.css b/np.plugin-test/requiredFiles/css.plugin.css index bcdf9b650..93f5ca5dc 100644 --- a/np.plugin-test/requiredFiles/css.plugin.css +++ b/np.plugin-test/requiredFiles/css.plugin.css @@ -91,6 +91,7 @@ select:after { margin-bottom: 25px; padding-top: 5px; padding-left: 20px; + padding-right: 20px; padding-bottom: 10px; /* Increased padding for top and bottom */ border-radius: 5px; transition: background-color 0.3s; @@ -111,7 +112,7 @@ select:after { } .pluginVersion, .pluginBy, .install, .installed { - margin-left: 15px; + margin-left: 10px; font-size: 0.9rem; color: #777; } @@ -161,6 +162,43 @@ table.w3-table thead { text-decoration: underline; } +.button { + text-decoration: none; /* Remove the underline */ + padding: 5px 10px; /* Some padding to make it look like a button */ + border-radius: 4px; /* Rounded corners */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* horizontal offset, vertical offset, blur radius, shadow color */ + font-size: 0.8rem; +} + +.install-btn { + color: #333; + text-align: center; + background-color: #f0b785; + border: 1px solid #e0e0e0; + border-radius: 4px; + transition: background-color 0.3s ease, color 0.3s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* horizontal offset, vertical offset, blur radius, shadow color */ +} + +.install-btn:hover { + background-color: #e09b61; + color: #fff; +} + +.documentation-link { + margin-left: 10px; + color: #888; /* A soft gray color */ + border: 1px solid #ccc; /* Light gray border */ + transition: background-color 0.3s, color 0.3s; /* Smooth hover transition */ +} + +.documentation-link:hover { + background-color: #f0f0f0; /* Slightly darker gray on hover */ + color: #555; /* Darker text color on hover */ +} + + + /* Making the sticky header responsive */