CINXE.COM
HubSpot Community - Share your review of Adobe Express - HubSpot Community
<!DOCTYPE html><html prefix="og: http://ogp.me/ns#" dir="ltr" lang="en" class="no-js"> <head> <title> HubSpot Community - Share your review of Adobe Express - HubSpot Community </title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> <meta content="Calling all content creators! With HubSpot's native integration, you can use Adobe’s AI content creation app to design and edit high-quality assets" name="description"/><meta content="width=device-width, initial-scale=1.0" name="viewport"/><meta content="2024-11-01T08:05:45-07:00" itemprop="dateModified"/><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><link href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774" rel="canonical"/> <meta content="https://community.hubspot.com/t5/user/viewprofilepage/user-id/435700" property="article:author"/><meta content="community.hubspot.com" property="og:site_name"/><meta content="article" property="og:type"/><meta content="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774" property="og:url"/><meta content="Brand Endorsement" property="article:tag"/><meta content="Community Champion Opportunities" property="article:section"/><meta content="Calling all content creators! With HubSpot's native integration, you can use Adobe’s AI content creation app to design and edit high-quality assets to perfection. Make stunning images and designs to use in social posts, marketing collateral, emails, and more with Adobe Express. We want to hear abo..." property="og:description"/><meta content="2024-11-01T13:00:00.144Z" property="article:published_time"/><meta content="2024-11-01T08:05:45-07:00" property="article:modified_time"/><meta content="Share your review of Adobe Express" property="og:title"/> <link class="lia-link-navigation hidden live-links" title="blog post Share your review of Adobe Express in Community Champion Opportunities" type="application/rss+xml" rel="alternate" id="link" href="/mjmao93648/rss/message?board.id=community-champion-opportunities&message.id=55"></link> <link href="/skins/6566941/cf3d09a7a299b32f8a1bbd2f844fb61f/hubspot.css" rel="stylesheet" type="text/css"/> <!-- Twitter Card metadata: For Japanese Blog Articles only --> <meta name="google-site-verification" content="JhdbLb5-5cPIvkeSYPUWX4n-wvBOsUlTzu7NjgCxLjQ" /> <script> window.$start = performance.now(); window.$stats = {}; // We need to define this BEFORE the lib is loaded so it initializes properly with this config window.__unocss = { theme: { breakpoints: { '@s': '(max-width: 479px)', '@m': '(max-width: 767px)', '@l': '(max-width: 1023px)', '@xl': '(min-width: 1200px)', }, px2rem: false, }, /** /* Within the RegEx rules below are named groups: /* - <d> stands for direction, think top, right, bottom, left, x, y /* - <g> stands for global, think values like auto|inherit|initial|revert|revert-layer|unset etc. /* - <m> stands for modifier, used for sub-properties like "-size" in "background-size" /* - <p> stands for property /* - <s> stands for selector where applicable /* - <u> stands for unit, default is px, no need to add that, for all other units, write them behind the value /* - <v> stands for value /*/ rules: [ // if you need a class to be !important simply prefix the class in HTMl with ! (it's automatic) // animation/transition utilities [/^a:(?<p>[a-z\-]+)?\/?(?<dur>[\d.ms]+)?\/?(?<e>[a-z\-]+)?\/?(?<del>[\d.ms]+)?$/, ([, p, dur, e, del]) => ( Object.entries({ 'transition-property': p || false, 'transition-duration': dur || '236ms', 'transition-timing-function': e || 'ease-in-out', 'transition-delay': del || false, }).reduce((rules, [prop, value]) => { if ( value ) { rules[prop] = /^[\d\.]+$/.test(value) ? `${value}ms` : value.replace(/(?<!var\()(?<v>--\S+)/, `var(${value})`); } return rules; }, {}) )], // border utilities // This one is a bit special, it's basically able to set a variety of properties if specified, the pattern is like // `b:<direction><color>/<width>/<style>/<radius>` if you want to omit certain values, you need the slash (/), // but with no value in between, so let's say you only want to set the border-style, you would do `b:///dashed`, // only the border-width: `t:/3`, only border-top-color: `b:tred`, all properties support CSS custom properties // and properties that have numeric values support also `calc()`, e.g. `b:t--color-red` and `t:blue/calc(100px/5)` // The order of properties is different than standard CSS which is `<width><style><color>` as in my experience // 90% of border styling are color, but we mostly write `1px solid <color>`, which is annoying, so this utility // defaults to `border-width: 1px` and `border-style: solid` which can of course easily be changed, but doing simply // `b:green` will result in a 1px solid green border in all directions, which cuts down on how much we have to type // if you don't like these defaults, it's quite simple to change it to whatever behaviour you like! // @limitations: This utility does NOT deal with any kind of border-image stuff, I can't even remember when I have // used this feature last, so not going to include it as a utility, if you really want to use it, write CSS... // @note: The strange looking negative lookaheads in the regex below are there to deal with 'reserved' word like // `transparent` or all kinds of named colors that would otherwise get chopped up by the direction indicators // workaround is simply using a hex color with 0 alpha, e.g. #0000... // TODO: Add in support for `outline`, don't want to make a separate util for it as it is relatively similar to // a border and mostly just annoying default focus behavior for lazy people who don't care about :focus... // TODO: Add support for border radius, probably best to take everything at the end (e.g. after <style>) and // split it by slashes, discard anything more than 4 and then apply them as border-radius would be applied /* TODO: think about how to implement fake directional borders (use box-shadow outlines for full ones) &r-fake { position: relative; &:after { color: red; content: ''; position: absolute; top: 0; bottom: 0; width: 1px; } } */ [/^b:(?<d>[bi][se]|r(?![ego])|t(?![r])|l(?![aei])|b(?![eilru])|y(?![e])|x|o(?![lr]))?\(?(?<v>(?:[a-z\-]+?\([\S]+?\)|[^\s\(\)]+?)+?)\)?$/, ([, d, v]) => ( ({ t: ['-top'], r: ['-right'], b: ['-bottom'], l: ['-left'], x: ['-right', '-left'], y: ['-top', '-bottom'], is: ['-inline-start'], ie: ['-inline-end'], bs: ['-block-start'], be: ['-block-end'], }[d] || ['']).reduce((rules, dir) => ( (v || '') .split(/\/?(?<v>[a-z\-]+\([\S]+?\)+(?![,\)_])|[^\s\/]+)\/?/g) .filter((x) => x.trim()) // handles shortcut values for inherit (`i`) and skip (`x`) // you can optionally specify border radius separated by _: `.b:red/1/solid/1px_2px_3px_4px` // but it has to be specified with the unit! slash-notation doesn't need the unit, will default to px // there's a special class `.b:none` to remove a border, but `.b:x/0` is shorter .map((x, i) => /^[\d\.]+$/.test(x) ? `${x}px` : x.replace(/(?<!var\()(--[^\s,/]+)/g, 'var($1)').replace(/_/g, ' ').replace(/^i$/g, 'inherit').replace(/^c$/g, 'currentColor').replace(/^s$/g, 'solid').replace(/^x$/g, '')) // we want always minimum 3 values, as we want to set defaults for width/style if not defined .reduce((arr, val, i, values) => (values.length < 3 ? [...values, ...Array(3-Math.min(values.length, 3)).fill(null)] : values), []) .reduce((ret, val, i, arr) => ({ ...ret, ...({ 0: !['', 'none'].includes(val) ? { [({o: 'outline'}[d] || `border${dir}`) + '-color']: val } : {}, 1: ![''].includes(val) ? { [({o: 'outline'}[d] || `border${dir}`) + '-width']: arr[0] === 'none' ? '0' : val || '1px' } : {}, 2: ![''].includes(val) ? { [({o: 'outline'}[d] || `border${dir}`) + '-style']: val || 'solid' } : {}, 3: { 'border-radius': val }, 4: { 'border-radius': `${ret['border-radius']} ${val}` }, 5: { 'border-radius': `${ret['border-radius']} ${val}` }, 6: { 'border-radius': `${ret['border-radius']} ${val}` }, }[i] || {}) }), {}) ), {}) )], /* OLD border version [/^(?<p>b):(?<d>[bi][se]|r(?![ego])|t(?![r])|l(?![aei])|b(?![eilru])|y(?![e])|x)?(?<g>inherit|initial|revert|revert-layer|unset)?(?<outline>o(?![lr]))?(?<color>[^\/\s:@]*)?\/?(?<width>[\d.]+|--[^\/\s:@]+|calc\(.*?\))?(?<u>[a-zA-Z%]+)?\/?(?<style>[^\/]*)?\/?(?<radius>[\S]+)?$/, ([, p, d, g, outline, color, width, u, style, radius]) => ( { undefined: [''], t: ['-top'], r: ['-right'], b: ['-bottom'], l: ['-left'], x: ['-right', '-left'], y: ['-top', '-bottom'], is: ['-inline-start'], ie: ['-inline-end'], bs: ['-block-start'], be: ['-block-end'], }[d].reduce((rules, dir) => { // global values like auto, initial, revert will be captured by the <u> group Object.entries({ [(outline ? 'outline' : `border${dir}`) + '-color']: color || g ? `${color?.replace(/(?<!var\()(?<v>--\S+)/, `var(${color})`) || ( g || '')}` : false, [(outline ? 'outline' : `border${dir}`) + '-width']: `${width?.replace(/(?<!var\()(?<v>--\S+)/, `var(${width})`) || '1'}${u || (!width?.startsWith('--') && !width?.includes('calc') ? 'px' : '')}`, [(outline ? 'outline' : `border${dir}`) + '-style']: `${style?.replace(/(?<!var\()(?<v>--\S+)/, `var(${style})`) || 'solid'}`, 'border-radius': radius ? radius.split(/\//g).map((v) => (/^[\d\.]+$/.test(v) ? `${v}px` : v.replace(/(?<!var\()(?<v>--\S+)/, `var(${v})`))).join(' ') : false, }).forEach(([prop, value]) => { if ( value ) { rules[prop] = g ? g : value; } }); return rules; }, {}) )], */ [/^b:(?<c>circle)|(?:rad\(?(?<v>(?:[a-z\-]+?\([\S]+?\)|[^\s\(\)]+?)+?)\)?)$/, ([, c, v]) => ( c ? { 'border-radius': '50%' } : (v || '') .split(/\/?(?<v>[a-z\-]+\([\S]+?\)+(?![,\)_])|[^\s\/]+)\/?/g) .filter((x) => x.trim()) .map((x, i) => /^[\d\.]+$/.test(x) ? `${x}px` : x.replace(/(?<!var\()(--[^\s,/]+)/g, 'var($1)').replace(/_/g, ' ').replace(/^i$/g, 'inherit').replace(/^c$/g, 'currentColor').replace(/^x$/g, '')) .reduce((ret, val, i) => ({ ...ret, ...({ 'border-radius': `${ret['border-radius'] || ''} ${val}` }) }), {}) )], // background: shorthand and advanced multi-value uncovered sub-property accessor // @note: if you encounter problems consider disabling this rule and swap it out // for the commented out one below that just takes in custom properties [/^bg:(?<m>-[a-z]+)?_?(?<v>['"0-9A-Za-z .,\/()\-_!%#]+)$/, ([, m, v]) => ({ [`background${m || ''}`]: v?.replace(/_/g, ' ')?.replace(/(?<!var\()(?<v>--\S+)/, `var(${v})`) })], // background[-prop]: variable/custom property interpolation, use like `bg:-image--var-name` // @note: swap with rule above if that one creates problems, it does this as well // [/^bg:(?<m>-[a-z]+)?_?(?<v>--.*|none)$/, ([, m, v]) => ({ // [`background${m || ''}`]: /^none$/.test(v) ? 'none' : `var(${v})` // })], // background-color: Use custom property syntax for actual colors like `bg:--c-green` [/^bg:(?<v>current|transparent)$/, ([, v]) => ({ 'background-color': v?.replace('current', 'currentColor') })], // background-position: we only support the 1 value version with utils /* TODO: think about implementing these modifiers from the SCSS version @include modifiers(( 'b': 'bottom', 'c': 'center', 'l': 'left', 'lb': 'left bottom', 'lt': 'left top', 'r': 'right', 'rb': 'right bottom', 'rt': 'right top', 't': 'top', ), $properties: 'background-position', $prefix: 'pos-', $separator: '-'); */ [/^bg:(?<v>center|top|right|bottom|left)$/, ([, v]) => ({ 'background-position': v })], // background-repeat: we only support the 1 value version with utils [/^bg:(?<v>repeat-x|repeat-y|repeat|space|no-repeat)$/, ([, v]) => ({ 'background-repeat': v })], // background-size: only word values suported with utils [/^bg:(?<v>auto|cover|contain)$/, ([, v]) => ({ 'background-size': v })], // box-shadow [/^bs:(?<p>oi|o)?\/?(?<v>[^\s]+)?$/, ([, p, v]) => ({ // `/([^\s\/]+\([\S]+?\))|\//g` => splits by slash except when they are between parenthesis like `calc(100px/2)` // `b`/`o` and `oi` are shortcuts for box-shadow outlines/borders (they don't affect the box-model), `o` creates // and outline outside of the box, `oi` one that does not go beyond the box-boundaries. This can be useful // for scenarios where you want a border in a certain state but not others and then have to set // `border-color: transparent` on those elements. But box-shadow outlines can't be direction controlled, // so they are only useful if the element should have a border on all sides 'box-shadow': ({ 'o': '0 0 0 ', 'oi': 'inset 0 0 0 ', }[p] || '') + v.split(/([^\s\/]+\([\S]+?\))|\//g).filter((x) => x).map((x) => (/^[\d\.]+$/.test(x) ? `${x}px` : x.replace(/(?<!var\()(--\S+)/, `var(${x})`))).join(' ') })], // cursor: just make sure you got the value right, unrestricted for brevity sake [/^c:(?<v>\S+)$/, ([, v]) => ({ 'cursor': v })], // display: There's a lot of different utilities summarized under the d: prefix, see preflights // still working on figuring out the best way to implement them as some might still require // browser prefixes, or target multiple properties etc. [/^(?<p>bg|d):(?<filter>blur|brightness|contrast|drop-shadow|grayscale|hue-rotate|invert|opacity|saturate|sepia)\(?(?<v>calc\([\S]+?\)|[^\s)]+)?\)?$/, ([, p, filter, v]) => ({ [`${p === 'bg' ? 'backdrop-' : ''}filter`]: `${filter}(${v?.startsWith('calc') ? v : v?.replace(/\//g, ' ') || ''})` })], ['d:b', { 'display': 'block' }], // `d:c(<name>/<type>)` || `d:c(<name>)` || `d:c` // https://developer.mozilla.org/en-US/docs/Web/CSS/container [/^d:c(?:\((?<n>[^\s\/]+)\/?(?<t>[^\s\/]+)?\))?$/, ([, n, t]) => ({ 'container': `${n || 'x'} / ${t || 'inline-size'}` })], ['d:i', { 'display': 'inline' }], ['d:ib', { 'display': 'inline-block' }], ['d:none', { 'display': 'none' }], ['d:hide', { 'visibility': 'hidden' }], ['d:show', { 'visibility': 'visible' }], ['d:invisible', { 'border': '0', 'clip': 'rect(1px, 1px, 1px, 1px)', 'height': '1px', 'outline': 'none', 'overflow': 'hidden', 'padding': '0', 'position': 'absolute', 'width': '1px', }], /* 1 */ ['d:f', { 'display': 'flex' }], ['d:fi', { 'display': 'flex inline' }], ['d:f-col', { 'flex-direction': 'column' }], ['d:f-row', { 'flex-direction': 'row' }], ['d:f-wrap', { 'flex-wrap': 'wrap' }], ['d:fc-items-center', { 'align-items': 'center' }], ['d:fc-justify-between', { 'justify-content': 'space-between' }], ['d:fc-justify-center', { 'justify-content': 'center' }], // If functions are used the value wrapping with () is mandatory! `d:f(col/1/0/calc(3px*100))` [/^d:f\(?(?<dir>col|row)?\/?(?<grow>[\d.]+)?\/?(?<shrink>[\d.]+)?\/?(?<basis>[\S]+)?\)+?$/, ([, dir, grow, shrink, basis]) => ( Object.entries({ 'flex-direction': { col: 'column', row: 'row' }[dir] || false, 'flex-grow': grow || false, 'flex-shrink': shrink || false, 'flex-basis': /^[\d\.]+$/.test(basis) ? `${basis}px` : (basis || '').replace(/(?<!var\()(?<v>--\S+)/, `var(${basis})`) || false, }).reduce((rules, [prop, value]) => { if ( value ) { rules[prop] = value; } return rules; }, {}) )], // order matters here, needs to be below the general `d:f` rule [/^d:f-(?<v>none|auto|initial)$/, ([, v]) => ({ 'flex': v })], // grid/flexbox gap, be careful though when using `d:g` it needs the gap defined within it's values // to properly calculate column width! so specify gap there if you use `d:g(<values>)`, it's fine to // use `d:gap` if you use `d:g` (without values) and define your template-columns yourself via CSS [/^d:gap(?<row>calc\([\S]+?\)|[^\s\/]+)?\/?(?<col>[^\s]+)?$/, ([, row, col]) => ( Object.entries({ 'row-gap': col ? row : false, 'column-gap': row ? col : false, 'gap': (row && !col) ? row : false, }).reduce((rules, [prop, value]) => { if ( value ) { rules[prop] = /^[\d\.]+$/.test(value) ? `${value}px` : value.replace(/(?<!var\()(?<v>--\S+)/, `var(${value})`); } return rules; }, {}) )], // flexbox/grid align and justify utils to be used like: // `.d:jself(center) or .d:j-self(center) or .d:js(center) or .d:aitems(start) or .d:a-items(start) or .d:ai(start)` [/^d:(?<d>a|j)-?(?<scope>c|i|s|t|content|items|self|tracks)[\(-](?<v>\S*?)\)?$/, ([, d, scope, v]) => ( { [`${{ a: 'align', j: 'justify' }[d]}-${{ c: 'content', i: 'items', s: 'self', t: 'tracks' }[scope] || scope}`]: v } )], // grid: format `d:g` to just set the display property, `d:g(<template-cols>/<gap/row-gap>?/<col-gap>?/<template-rows>?)` // TODO: Keep an eye on `grid-template-rows: masonry` support, it would be awesome, but is not supported as of 2023 // it's technically implemented as the 4th via `grid-template-rows`: That one is a bit different, can't do it with // optional gap values, so even if no gaps should be defined, it needs to be written like `d:g(12/0/0/masonry)` // there is a special value `equal` that will make all rows equal height to the tallest one: `d:g(12/0/0/equal)` // the 4th value is basically a free-for-all, you can go crazy with stuff like `repeat(auto,minmax(calc(100vh/3),1fr)))` // to define the grid-template-rows, I use it rarely, but it's there if needed. [/^d:(?<p>g|gi)(?:\((?<v>[^\s]+)?\))?$/, ([, p, v], ctx) => ( ({ 'g': [ ['display', 'grid'] ], 'gi': [ ['display', 'inline-grid'] ], }[p] || []).reduce((rules, [prop, value]) => { if ( value ) { //console.log('rule matcher args', p, v, ctx); // set the display property rules[prop] = value; // parse the grid config values and inject additional properties into the outer reduced array // depending on how many config values were provided, do not give unit to first value automatically // as it is the number of grid-columns and needs to be a unitless value for the internal calc(), it // can be a custom CSS property/variable or calc() resolving in a unitless number itself though, // meaning: `d:g(--var-cols/12px/calc(100px/4))` or `d:g(calc(48/4)/12px/calc(100px/4))` are fine. // Does it make sense to calc() within calc()? not sure... but it's possible ;). // If the first value has a unit, no automatic cols calculation will happen, but the value will // be used as a min-width of a grid-column letting the browser do the heavy lifting, this can be // very helpful not having to define any kind of @media queries for responsiveness! // `d:g(264px)` => `grid-template-columns: repeat(auto-fit, minmax(264px, 1fr));` // auto-fill vs auto-fit: https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/ if ( v ) { v = v.split(/\/?(?<v>[a-z\-]+\([\S]+?\)(?![,\)])+|[^\s\/]+)\/?/g).filter(x => x).map((x, i) => /^[\d\.]+$/.test(x) && i !== 0 ? `${x}px` : x.replace(/(?<!var\()(?<v>--\S+)/, `var(${x})`)); // The last value can be 'fit' or 'fill' regardless of position to select auto-filling algo const f = /(fit|fill)/.test(v.at(-1)) ? v.pop() : 'fit'; const props = ({ 1: [['grid-template-columns', `repeat(auto-${f}, minmax(${/[\d\.]+[a-z%]+$/.test(v[0]) ? v[0] : `calc(100%/${v[0]})`}, 1fr))`]], 2: [['grid-template-columns', `repeat(auto-${f}, minmax(${/[\d\.]+[a-z%]+$/.test(v[0]) ? v[0] : `calc(100%/${v[0]} - ${v[1]})`}, 1fr))`], ['gap', v[1]]], 3: [['grid-template-columns', `repeat(auto-${f}, minmax(${/[\d\.]+[a-z%]+$/.test(v[0]) ? v[0] : `calc(100%/${v[0]} - ${v[2]})`}, 1fr))`], ['row-gap', v[1]], ['column-gap', v[2]]], 4: [['grid-template-columns', `repeat(auto-${f}, minmax(${/[\d\.]+[a-z%]+$/.test(v[0]) ? v[0] : `calc(100%/${v[0]} - ${v[2] || 0})`}, 1fr))`], ['row-gap', v[1]], ['column-gap', v[2]], ( v[3] === 'equal' ? ['grid-auto-rows', '1fr'] : ['grid-template-rows', ( v[3] ? v[3].replace(/_/g, ' ') : null )] )], }[Math.min(v.length, 4)] || []).forEach(([_p, _v]) => (rules[_p] = _v)); } } return rules; }, {}) )], [/^d:(?:g-?)?(?<p>col|row)\(?(?<v>\S+?)\)?$/, ([, p, v]) => ({ [`grid-${p === 'col' ? 'column' : 'row'}`]: v.replace(/_/g, ' ') })], // grid col/row span // regex captures everything after `d:span<v>` (or within parenthesis `d:span(<v>)`), syntax is: // variant a): `d:span<v[<col[<start>-<end?>]>/<row?[<start>-<end?>]>]>` // variant b): `d:span(<v[<col[<start>-<end?>]>/<row?[<start>-<end?>]>]>)`, // we deal with the value inside the matcher function and split it into it's components // TODO: Re-evaluate if the separate util is worth it as we can simply use `.d:g-col(<v>` and `.d:g-row(<v>)` [/^d:(?:g-?)?span\(?(?<v>(?:[a-z\-]+?\([\S]+?\)|[^\s\(\)]+?)+?)\)?$/, ([, v]) => ( (v || '') .split(/\/?(?<v>[a-z\-]+\([\S]+?\)+(?![,\)_])|[^\s\/]+)\/?/g) .filter(x => x.trim()) // don't need the auto-pixelator here, span values are unitless, but we support CSS vars //.map((x, i) => /^[\d\.]+$/.test(x) ? `${x}px` : x.replace(/(?<!var\()(--[^\s,/]+)/g, 'var($1)').replace(/_/g, ' ')) .map((x, i) => x.replace(/(?<!var\()(--[^\s,/]+)/g, 'var($1)')) .reduce((rules, value, i) => ({ ...rules, ...({ // if useing CSS vars, specify the full value, like this it's most flexible 0: { 'grid-column': value.includes('var(') ? value : value.split('-').map((x, i, a) => a.length === 1 ? `span ${x}` : x ).join(' / ') }, 1: { 'grid-row': value.includes('var(') ? value : value.split('-').map((x, i, a) => a.length === 1 ? `span ${x}` : x ).join(' / ') }, }[i] || {}) }), {}) )], ['d:noverflow', { 'overflow': 'hidden' }], // order (flexbox/grid), syntax: `d:order(?-?<v>)?` [/^d:order\(?(?<v>[-\d]+)\)?$/, ([, v]) => ({ 'order': v })], // display => overflow(-[x|y]) auto [/^d:scroll(?<m>-[xy])?$/, ([, m]) => ({ [`overflow${m || ''}`]: 'auto' })], ['d:scroll', { 'overflow': 'auto' }], ['d:scroll(x)', { 'overflow-x': 'auto' }], ['d:scroll(y)', { 'overflow-y': 'auto' }], // it's a bit tricky to yield those ::selectors, but this is how it can be done... // question is: should we just preflight those as raw CSS instead of doing constructs like this? // the strings ship with the bundle, one way or the other, but as preflights at least the utils // are readable... and after all, there's nothing dynamic about them, simply creating overhead here // futhermore it's questionable to do it like that as it will most likely result in more // characters/bytes shipped than if it was just pure CSS... [/^(?<sel>d:scroll-nobar)$/, ([, sel], context) => { return `${context.constructCSS({ 'scrollbar-width': 'none', /* Firefox */ '-ms-overflow-style': 'none', /* IE 10+ */ })}\n.${CSS.escape(sel)}::-webkit-scrollbar { width: 0; height: 0; }` /* WebKit */ }], // pointer-events [/^e:(?<v>\S+)$/, ([, v]) => ({ 'pointer-events': v })], // list-style: order of values is type | image | position, escape quotes with ^ [/^l:(?<v>.{3,}?)?(?:_(?<url>.+?))?(?:_(?<pos>outside|inside))?$/, ([, v, url, pos]) => ({ 'list-style': `${v?.replaceAll('^', '"')} ${url || ''} ${pos || ''}` })], // margin and padding: supports basics plus custom properties (variables), optional directions and global word values [/^(?<p>m|p):(?<d>r(?!e)|[ltbxy]|[bi][se])?(?<v>(?:(?:-(?!-))?[\d._]+)|--\S+|calc\(.*?\))?(?<u>[a-zA-Z%]+)?$/, ([, p, d, v, u]) => ( { undefined: [''], t: ['-top'], r: ['-right'], b: ['-bottom'], l: ['-left'], x: ['-right', '-left'], y: ['-top', '-bottom'], is: ['-inline-start'], ie: ['-inline-end'], bs: ['-block-start'], be: ['-block-end'], }[d].reduce((rules, dir) => { // global values like auto, initial, revert will be captured by the <u> group // TODO: not sure what the `.replaceAll('_', ' ')` is for, maybe a copy paste leftover from another rul? rules[`${{m: 'margin', p: 'padding'}[p]}${dir}`] = `${v?.replaceAll('_', ' ')?.replace(/(?<!var\()(?<v>--\S+)/, `var(${v})`) || ''}${u || (!v?.startsWith('--') && !v?.includes('calc') ? 'px' : '')}`; return rules; }, {}) )], // opacity // technically the spec allows for percantage values, but we convert anything to float, makes things easier [/^o:(?<v>[\d.%]+|--\S+|calc\(.*?\))?$/, ([, v]) => ({ 'opacity': isNaN(parseFloat(v)) ? (v?.replace(/(?<!var\()(?<v>--\S+)/, `var(${v})`) || '') : ( parseFloat(v) <= 1 ? parseFloat(v) : parseFloat(v)/100 ) })], // position utils [/^(?<p>pos):(?<d>r(?![e]|$)|[ltb])?(?<v>[\S]+)?$/, ([, p, d, v]) => ( { 'a': [ ['position', 'absolute'] ], 'f': [ ['position', 'fixed'], ['backface-visibility', 'hidden'] ], 'r': [ d ? ['right', null] : ['position', 'relative'] ], 's': [ ['position', 'sticky'] ], 'center': [ ['position', 'absolute'], ['top', '50%'], ['left', '50%'], ['transform', 'translate(-50%, -50%)'] ], 'center-x': [ ['position', 'relative'], ['left', '50%'], ['transform', 'translateX(-50%) perspective(1px)'] ], 'center-y': [ ['position', 'relative'], ['top', '50%'], ['transform', 'translateY(-50%) perspective(1px)'] ], 'reset': [ ['position', 'static'] ], // direction utils, set the value falsy so we can set it within reduce 't': [ ['top', null] ], 'b': [ ['bottom', null] ], 'l': [ ['left', null] ], undefined: [], }[d || v].reduce((rules, [prop, value]) => { if ( value ) { rules[prop] = value; } else if ( d && v ) { rules[prop] = /^[\d\.]+$/.test(v) ? `${v}px` : v.replace(/(?<!var\()(?<v>--\S+)/, `var(${v})`); } return rules; }, {}) )], /** /* FLUID MEDIA CONTENT UTILS (IMG, VIDEOS, IFRAMES etc.) /* 1. Element will be stretched to the full extend of the nearest realtively-positioned /* ancestor. /* 2. Element will be stretched to the entire viewport and follow the user's scrolling, /* good for modal windows and overlays /* 3. Add this class to the element that contains the fluid media content and do not forget /* to define the aspect ratio like `.scale:frame(16/9)`. /* 4. Add this class to the element that should scale in a specific ratio, useful for /* fluid videos, iframes (maps anybody?), embeds but also images. /* 5. Allows an image to be responsvie up to its container width but not exceeding /* it's native size. /*/ [/^scale:(?<v>[^\s(]+)\(?(?<ratio>[\d\/]+)?\)?$/, ([, v, ratio]) => ( { 'fit': [ ['bottom', '0'], ['left', '0'], ['margin', 'auto'], ['position', 'absolute'], ['right', '0'], ['top', '0'] ], /* 1 */ 'fullscreen': [ ['backface-visibility', 'hidden'], ['bottom', '0'], ['left', '0'], ['margin', 'auto'], ['position', 'fixed'], ['right', '0'], ['top', '0'] ], /* 2 */ 'frame': [ ['display', 'block'], ['position', 'relative'], ['aspect-ratio', (ratio ? `${ratio}` : null) ] ], /* 3 */ // The padding hack is old-school now, we now have native aspect ratio, but for reference // I'll leave those here anyways, the calculation goes as follows: `9 / 16 * 100% = 56.25%` // So if a custom aspect ratio is needed one could simply apply `p:tcalc(2/12*100%)` //'frame(16/9)': [ ['padding-top', '56.25%'] ], //'frame(3/2)': [ ['padding-top', '66.66666%'] ], //'frame(4/3)': [ ['padding-top', '75%'] ], 'content': [ ['bottom', '0'], ['left', '0'], ['margin', 'auto'], ['position', 'absolute'], ['right', '0'], ['top', '0'] ], /* 4 */ 'img': [ ['display', 'block'], ['height', 'auto'], ['max-width', '100%'] ], /* 5 */ undefined: [], }[v].reduce((rules, [prop, value]) => { if ( value ) { rules[prop] = value; } return rules; }, {}) )], /** /* TEXT UTILS /* /* 1. Enables font kerning in all browsers. /* see also: http://blog.typekit.com/2014/02/05/kerning-on-the-web/ /* 2. Ensure that the node has a maximum width after which truncation can occur. /* 3. Fix for IE 8/9 if 'word-wrap: break-word' is in effect on ancestor nodes. /* 4. A little helper to increase the font-weight slightly without having to rely /* on font-style. Especially useful to increase readability of very small type! /*/ // This one is a bit special, it's basically able to set a variety of properties if specified, the pattern is like // `t:<color>/<font-size>/<line-height>/<font-weight>/<font-style>/<font-family>` if you want to omit certain values, // you need the slash (/), but with no value in between, so let's say you only want to set the font family, // you would do `t://///Arial`, only the font-weight: `t:///700`, only the color: `t:red`, all properties // support CSS custom properties and properties that have numeric values support also `calc()`, e.g. `t:--color-red` // and `t:blue/calc(100px/5)/calc(100px/4)` // TODO: support letter-spacing here or in separate utility? // TODO: support other functions than calc(), clamp() in particular, but basically all CSS math functions [/^(?<p>t):(?<color>[^\/\s:@]*)?\/?(?<size>[\d.]+|--[^\/\s:@]+|calc\(.*?\))?(?<u>[a-zA-Z%]+)?\/?(?<lh>[\d.]+|--[^\/\s:@]+|calc\(.*?\))?(?<lhu>[a-zA-Z%]+)?\/?(?<weight>[\d.]+|--[^\/\s:@]+|calc\(.*?\))?\/?(?<style>[^\/\s:@]*)?\/?(?<family>[^\/\s:@]*)?$/, ([, p, color, size, u, lh, lhu, weight, style, family]) => ( Object.entries({ 'color': color ? `${color?.replace(/(?<!var\()(?<v>--\S+)/, `var(${color})`) || ''}` : false, 'font-size': size || size === '0' ? `${size?.replace(/(?<!var\()(?<v>--\S+)/, `var(${size})`) || ''}${u || (!size?.startsWith('--') && !size?.includes('calc') ? 'px' : '')}` : false, 'line-height': lh || lh === '0' ? `${lh?.replace(/(?<!var\()(?<v>--\S+)/, `var(${lh})`) || ''}${lhu || ''}` : false, 'font-weight': weight ? `${weight?.replace(/(?<!var\()(?<v>--\S+)/, `var(${weight})`) || ''}` : false, 'font-style': style ? `${style?.replace(/(?<!var\()(?<v>--\S+)/, `var(${style})`) || ''}` : false, 'font-family': family ? `${family?.replace(/(?<!var\()(?<v>--\S+)/, `var(${family})`) || ''}` : false, }).reduce((rules, [prop, value]) => { if ( value ) { rules[prop] = value; } return rules; }, {}) )], // font-family // This utility isn't really meant to define full font-stacks, you may try to use the general utility // like `t://///Arial` even though it's questionable to use like that. By default it just applies the global CSS // keywords for font-family, including such as ui-serif etc. which are not supported by browsers today // the power of this one is that it will also look for pre-defined custom CSS properties/variables at // :root level with the specific pattern of `---t-family-<keyword>`, if found it will use the variable // value. In this way font stacks can easily be defined via such variables. Besides the standrad spec // keywords, it also supports some custom short ones: sans => sans-serif, mono => monospace, ui => system-ui // TODO: Make variable names configurable via theme or at least the prefix and allow passing in the specific // `--t-family-<name>` variable via CSS class as well, like this we could reference full font stacks without // making things too ugly? How can we access theme within rules? [/^t:(?<v>serif|sans-serif|sans|monospace|mono|cursive|fantasy|system-ui|ui|ui-serif|ui-sans-serif|ui-monospace|ui-rounded|emoji|math|fangsong)$/, ([, v]) => ({ 'font-family': !!getComputedStyle(document.documentElement).getPropertyValue(`---t-family-${v}`) ? `var(---t-family-${v})` : v.replace(/^(sans|mono|ui)$/gi, x => ({'sans': 'sans-serif', 'mono': 'monospace', 'ui': 'system-ui'}[x])) })], // font-style // Shortcuts for what is doable with the 'general' utility above as well, the key word `normal` // is already used by the font-weight utility below, so `t:regular` is used to set `font-style: normal` // specifying the angle for `font-style: oblique 20deg` is supported as well with `t:oblique/20` // valid angle values are +-90deg, but most fonts don't support it anyways... [/^t:(?<v>italic|oblique|regular)\/?(?<deg>-?\d{1,2})?$/, ([, v, deg]) => ({ 'font-style': `${v.replace('regular', 'normal')}${deg ? ` ${deg}deg`: ''}` })], // font-weight // Those are shortcuts which can all be achieved by using the above 'general' utility as well, but they // are still included for convenience, lighter/bolder are relative-to-parent font-weights (see MDN) // this util has a feature where it looks for custom CSS properties (variables) defined at `:root` level // that match the specific pattern of `---t-weight-<word|weight>`, if found, it will use those instead of // the actual word or numeric weight, this allows to re-define what those words/weights mean in terms of // actual font-weight, e.g. if default for `font-weight: normal` is 400, if you set `---t-weight-normal: 300` // at `:root` level and use the utility `t:normal` on some element, it's font-weight will now be 300 // these custom CSS properties/variables are intentionally prefixed with 3! dashes (---) to hopefully avoid // any conflicts with 'regularly' defined custom CSS variables which accidentially have the same name! [/^t:(?<v>lighter|bolder|thin|normal|bold|heavy|\d00)$/, ([, v]) => ({ 'font-weight': !!getComputedStyle(document.documentElement).getPropertyValue(`---t-weight-${v}`) ? `var(---t-weight-${v})` : v.replace('thin', '100').replace('heavy', '900') })], // `t:boldest` applies a text-shadow which adds to the visual boldness of text regardless of font-weight applied ['t:boldest', { 'text-shadow': '0 0 0.3px currentColor' }], // text-align: we only support the 1 value version with utils, the string thing seems fringe anyways // additionally we convert left/right 'absolute' values to start/end which is text-direction aware // `justify-all` and `match-parent` are in the spec but seem very poorly supported! [/^t:(?<v>start|end|left|right|center|justify|justify-all|match-parent)$/, ([, v]) => ({ 'text-align': v.replace('left', 'start').replace('right', 'end') })], // text-decoration // We don't support multiple text-decoration-line values, if you really need that, specify it via multiple classes // text-decoration-skip is also not supported as browser support is 0, but text-decoration-skip-ink is supported // There is a shortcut `t:del` which translates to `t:line-through` automatically. // `blink` is deprecated and only Opera and Safari still support it...We don't, it's bullshit UX anyways... // A text-decoration-line value has to be defined, otherwise this util won't catch, the other additional properties // can be omitted, the format is `t:<decoration-line>/<style>/<color>/<thickness>/<skip-ink> [/^t:(?<line>none|underline|overline|line-through|del)\/?(?<style>solid|double|dotted|dashed|wavy|--[^\/\s:@]+)?\/?(?<color>[^\/\s:@]*)?\/?(?<width>[\d.]+|--[^\/\s:@]+|calc\(.*?\))?(?<u>[a-zA-Z%]+)?\/?(?<skip>none|auto|all|--[^\/\s:@]+)?$/, ([, line, style, color, width, u, skip]) => ( Object.entries({ 'text-decoration-line': line ? `${line?.replace(/(?<!var\()(?<v>--\S+)/, `var(${line})`).replace('del', 'line-through') || ''}` : false, 'text-decoration-style': style ? `${style?.replace(/(?<!var\()(?<v>--\S+)/, `var(${style})`) || ''}` : false, 'text-decoration-color': color ? `${color?.replace(/(?<!var\()(?<v>--\S+)/, `var(${color})`) || ''}` : false, 'text-decoration-thickness': width || width === '0' ? `${width?.replace(/(?<!var\()(?<v>--\S+)/, `var(${width})`) || ''}${u || (!width?.startsWith('--') && !width?.includes('calc') ? 'px' : '')}` : false, 'text-decoration-skip-ink': skip ? `${skip?.replace(/(?<!var\()(?<v>--\S+)/, `var(${skip})`) || ''}` : false, }).reduce((rules, [prop, value]) => { if ( value ) { rules[prop] = value; } return rules; }, {}) )], // text-tranform [/^t:(?<v>capitalize|caps|lowercase|lcase|uppercase|ucase)$/, ([, v]) => ({ 'text-transform': v.replace('caps', 'capitalize').replace('lcase', 'lowercase').replace('ucase', 'uppercase') })], ['t:break', { 'word-wrap': 'break-word' /* 4 */ }], ['t:nowrap', { 'white-space': 'nowrap' }], // TODO: not sure if this even does something visually, what is it's purpose? remove it? ['t:kern', { '-webkit-font-feature-settings': '"kern" 1', 'font-feature-settings': '"kern" 1', '-webkit-font-kerning': 'normal', 'font-kerning': 'normal', 'text-rendering': 'optimizeLegibility' }], // 1 ['t:truncate', { 'max-width': '100%' /* 2 */, 'overflow': 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap', 'word-wrap': 'normal' /* 3 */ }], // transform // transform function values are very diverse, just write them as usual separated with commas just NO spaces! // You can use `calc()` or custom css properties `--some-var` if they are allowed to be used for function values // we can't cast non-pixel values to pixels either, because different functions have different inputs, like `scale()` // for example expects unitless values, so for the values, you do need to specify the unit if appropriate // Does NOT support specifying multiple transform functions in one call, write proper CSS for a use-case like that! // Does support transform-origin/box/style with the appropriate prefix `b|o|s` e.g. `.tr:o(center/50px)` // note that the classname consistently has to encapsulate the value in parenthesis, e.g. `.tr:rotate(120deg)`, but // also `.tr:o(--some-var/calc(100px/2))` => `transform-origin: var(--some-var) calc(100px/2);` // The order of the transform functions in the RegEx matters, so don't touch! [/^tr:(?<p>[bo]|s(?![ck]))?(?<fn>matrix3d|matrix|none|perspective|rotate3d|rotateX|rotateY|rotateZ|rotate|scale3d|scaleX|scaleY|scaleZ|scale|skewX|skewY|skew|translate3d|translateX|translateY|translateZ|translate)?\((?<v>[^\s]+)?\)$/, ([, p, fn, v]) => ({ [{ b: 'transform-box', o: 'transform-origin', s: 'transform-style', undefined: 'transform', }[p]]: p // `/\/(?![\S]+\))/g` => splits by slash except when they followed by `)` like `calc(100px/2)` ? v.split(/\/(?![\S]+\))/g).map((x) => (/^[\d\.]+$/.test(x) ? `${x}px` : x.replace(/(?<!var\()(--\S+)/, `var(${x})`))).join(' ') : fn.match(/none/) ? fn : `${fn}(${v?.replace(/(?<!var\()(?<v>--\S+)/g, `var(${v})`)})` })], // width & height [/^(?<p>w|h):(?<m>min|max|screen)?(?<v>[\d.]+|--\S+|calc\(.*?\))?(?<u>[a-zA-Z%]+)?$/, ([, p, m, v, u]) => ( { undefined: [''], min: ['min-'], max: ['max-'], screen: [(rules) => (rules[`${{w: 'width', h: 'height'}[p]}`] = `100v${p}`)] }[m].reduce((rules, mod) => { if ( mod instanceof Function ) { mod(rules); return rules; } // global values like auto, initial, revert will be captured by the <u> group // replacing _ with ' ' allows for escaping required spaces in calc +/- operations like calc(100% - 32px) rules[`${mod}${{w: 'width', h: 'height'}[p]}`] = `${v?.replace(/(?<!var\()(?<v>--\S+)/, `var(${v})`).replaceAll('_', ' ') || ''}${u || (!v?.startsWith('--') && !v?.includes('calc') ? 'px' : '')}`; return rules; }, {}) )], [/^z:(?<v>.+)$/, ([, v]) => ({ 'z-index': v.replace(/(?<!var\()(?<v>--\S+)/, `var(${v})`) })], // debug/dev tools, simply add the class `?` to an element and it will make itself very visible ;) // It might very often be the case that you need to overrule existing styles that conflict with the // dev tool class, so just prefix with `!` to make everything !important. [/^(\?)$/, (_, { constructCSS, generator }) => ( `@keyframes __imhere{0%{box-shadow:inset 0 0 0 2px red}100%{box-shadow:inset 0 0 0 6px yellow}}\n${constructCSS({ animation: '__imhere 0.5s ease-in-out alternate infinite' })}` )], // create a box around an element, good for highlighting stuff // arrows can be useful for screenshots, try it out with `?[]>`, `?[]^`, `?[]<`, `?[]>t` etc. [/^(?<sel>\?\[(?<inset>[0-9-]+)?\/?(?<width>\d+)?\](?:(?<arrow>\>|\<|\^)(?<top>t)?)?\/?(?<hue>\d+)?\/?(?<opacity>[\d.%]+)?)$/, ([, sel, inset, width, arrow, top, hue, opacity], ctx) => ( `${ctx.constructCSS({ overflow: 'visible !important', position: 'relative', })}${ arrow ? `\n.${CSS.escape(sel)}:before { color: hsl(${hue || 0} 100% 50%) !important; content: '↗' !important; font-size: 36px !important; font-weight: 300 !important; line-height: 1 !important; position: absolute; ${top ? `top: -${Math.abs(inset || 0)}px` : `bottom: -${Math.abs(inset || 0)}px`}; ${arrow === '<' ? `right: -${Math.abs(inset || 0)}px` : `left: ${ arrow === '^' ? '50%' : `-${Math.abs(inset || 0)}px` }`}; transform: translate(${arrow === '<' ? '150%' : `${arrow === '^' ? '-50%' : '-150%'}`}, ${ top ? '-125%' : '125%'}) ${arrow === '<' ? 'rotate(-90deg)' : arrow === '^' ? 'rotate(-45deg)' : ''} ${ top ? `scale(${arrow === '<' ? '-1,1' : arrow === '^' ? '-1' : '1,-1'})` : ''}; z-index: 9999 !important; }` : ''}\n.${CSS.escape(sel)}:after { box-shadow: inset 0 0 0 ${Math.abs(inset || 0)}px hsl(${hue || 0} 100% 50% / ${opacity || .1}) !important; content: '' !important; inset: ${inset || 0}px !important; margin: 0 !important; padding: 0 !important; outline: ${width || 1}px solid hsl(${hue || 0} 100% 50%) !important; position: absolute !important; transform: none !important; z-index: 9999 !important; }` )], // and the dev grid overlay [/^(?<sel>\?#\(?(?<s>\d+)?\/?(?<o>[\d.]+)?\/?(?<h>[\d]+)?(?<r>r)?\)?)$/, ([, sel, s, o, h, r], ctx) => ( `${ctx.constructCSS({ 'position': 'relative', })}\n.${CSS.escape(sel)}:before { background-image: linear-gradient(hsl(${ h ? h : '0'} 100% ${ h ? '50%' : '0%'} / ${ o ? o : '.12'}) 1px, transparent 1px), linear-gradient(90deg, hsl(${ h ? h : '0'} 100% ${ h ? '50%' : '0%'} / ${ o ? o : '.12'}) 1px, transparent 1px); /*background-position: -1px -1px, -1px -1px;*/ background-size: ${ s ? `${s}px ${s}px, ${s}px ${s}px` : '24px 24px, 24px 24px'}; box-shadow: inset 0 0 0 1px hsl(${ h ? h : '0'} 100% ${ h ? '50%' : '0%'} / ${ o ? o : '.12'}); content: ''; position: absolute; top: 0; bottom: 0; left: 0; right: 0; width: 100%; z-index: 10000; }\n.${CSS.escape(sel)} > [class*="${CSS.escape('b:')}"] { border-color: red !important; border-radius: 0 !important; }\n.${CSS.escape(sel)} > :not([class*="${CSS.escape('b:')}"]) { border-radius: 0 !important; box-shadow: inset 0 0 0 1px red !important; }${ r ? `\n.${CSS.escape(sel)}:after { background-image: linear-gradient(hsl(0 100% 0% / 1) 1px, transparent 1px), linear-gradient(90deg, hsl(0 100% 0% / 1) 1px, transparent 1px), linear-gradient(90deg, hsl(0 100% 0% / 1) 1px, transparent 1px), linear-gradient(-90deg, hsl(0 100% 0% / 1) 1px, transparent 1px); background-position: left bottom; background-repeat-y: no-repeat; background-size: ${ s ? `${s/2}px 6px, ${s/2}px 6px, ${s}px 11px, 100% 11px` : '6px 6px, 6px 6px, 12px 11px, 100% 11px'}; content: attr(data-width); display: block; font-size: 10px; line-height: 1; padding-bottom: 16px; position: absolute; top: 0; left: 0; text-align: center; transform: translateY(-125%); width: 100%; }` : '' }` )], ], shortcuts: [ // you could still have object style /*{ 'forum-nav-bar': '!bg:cyan', },*/ // dynamic shortcuts //[/^btn-(.*)$/, ([, c]) => `bg-${c}-400 text-${c}-100 py-2 px-4 rounded-lg`], ], variants: [ // Allows targeting child/sub elements of the element the util is applied to // Any valid combinator (or none) are supported, just add the child selector between pipes `|` // and add the util after, give that class to the wrapping element of whatever should be targeted // this helps a lot for use-cases where every child (imagine a ul>li structure) should get the // same styles, with regular utils, every li has to have all util classes, which is very // redundant and one of the major downsides of utility-based CSS approaches. With the help of this // variant this is a thing of the past as only one class has to be defined on the parent targeting // any and all decendent nodes with the appropriate selector. // `|>li|bg:red` => `.\|\>li\|bg\:red > li { background-color: red; }` { name: 'combinators', match: (matcher) => { const rx = /^\|(?<combinator>[>+~])?(?<selector>\S+)\|(?<util>\S+)$/; if ( !rx.test(matcher) ) { return matcher; } const { groups: { combinator, selector, util } } = matcher.match(rx); // the combinator is optional, but makes no sense to continue without selector or util if ( !selector || !util ) { return matcher; } //console.log('Found combinator match', `(selector) ${combinator} ${selector}`, util); return { matcher: util, selector: (s) => `${s} ${combinator || ''} ${selector}`, } }, multipass: false, //order: -1, }, // Targets basically every advanced CSS selector and pesudo content if they are prefixed with a colon `:` // this is very powerful as it allows targeting stuff like `:not(:last-child)` purely through CSS // classes, like `.b:b1/red::not(:last-child)` or `.t:bold::after` the double colon separator is needed // so we can actually do stuff like `:not(:last-child)`, which wouldn't work with a single colon (or put // differently: I'm too dumb to figure out the regex to do that!) // TODO: Figure out why those don't work in conjunction with combinator variants { name: 'pseudo', match: (matcher, ctx) => { const rx = /^[^:|]+:[^:]+(?<pseudoclass>:\S+)$/; //if ( !/:(:.+)$/.test(matcher) ) { return matcher; } if ( !rx.test(matcher) ) { return matcher; } const { groups: { pseudoclass } } = matcher.match(rx); // You can define any custom pseudo-classes and their selector interpolations here const custom = function(pc, s) { return { ':hocus': `${s}:hover, ${s}:focus`, ':hocus-within': `${s}:hover, ${s}:focus-within`, }[pc]; }; //console.log('Pseudoclass match found', matcher, pseudoclass); return { // slice pseudo-class and pass to the next variants and rules matcher: (pseudoclass ? matcher.slice(0, -(pseudoclass.length)) : matcher), selector: (s) => (pseudoclass ? custom(pseudoclass, s) || `${s}${pseudoclass}` : s), } }, // doesn't really work yet, probably my fault (regex?), not that important, specify two classes meanwhile multipass: false, //order: -1, }, // Converts `©(width>500px)` to `@container(width>500px)` to be dealt with by the atrule variant // => meh: I prefer the shortcut atrules like @c, @s, @l... /*{ name: '©rules', match: (matcher, ctx) => { if ( !matcher.includes('©') ) { return matcher; } return { matcher: matcher.replace(/©/g, '@container'), } }, multipass: false, },*/ // @rules { name: '@rules', match: (matcher, ctx) => { const rx = /^\S+:\S+(?<atrule>@[^:]+)/; if ( !rx.test(matcher) ) { return matcher; } const { groups: { atrule } } = matcher.match(rx); /** /* We want to support a variety of @rules, not all make sense to be specified via classes /* but the goal is to support: /* - pre-configured theme breakpoints which are mapped to @media, they are simply a string /* with the exact syntax of a regular CSS media query, this allows for complex breakpoints /* with logical operators etc. without creating a massive parsing overhead here. /* - on-the-fly evaluated breakpoints to be specified like CSS 4 range queries: /* `@(<|<=|=|>=|>)<number><unit>(width|height)(<|<=|=|>=|>)<number><unit>` supporting `width` and `height` /* There's a limit on how far I think it makes sense to go with supporting the offical spec /* it would get highly complex to implement all of it, so for now as we want to translate /* those expressions into the wider supported min-<prop> max-<prop> syntax, once browser support /* for range syntax is not that recent anymore, it should be easy to simply evaluate and forward /* complex range syntax queries with multiple conditions and operators. An implementation detail /* worth noting is the use of `=` (which usually makes 0 sense for media queries, who wants to /* specify an exact pixel value where styles apply?) to communicate values for props, so for example /* you'd specify the orientation feature (normally `@media (orientation: landscape)`) like /* `@orientation=landscape` or `@media(orientation=landscape)` or `@(orientation=landscape) (all valid) /* - @supports(display=grid) => https://css-tricks.com/how-supports-works/ /* - @layer(name), /* - @container((<|<=|=|>=|>)<number><unit>(width|height)(<|<=|=|>=|>)<number><unit>), /* => if none of those 3 keywords are found, @media, is assumed by default, as values can /* contain colons (:) we 'escape' those within the CSS class names with `=` as the colon /* is already used for pseudo classes this goes also for stuff like `@supports(selector(=last-child))` /* and is then translated to `@supports (selector(:last-child))` /*/ function transform(atrule) { //console.log('transform', atrule, ctx.theme.breakpoints[atrule], ctx.theme); // Check theme config for matching breakpoint (with or without the @) and return that early if ( ctx.theme.breakpoints[atrule] || ctx.theme.breakpoints[atrule.substring(1)] ) { return `@media ${(ctx.theme.breakpoints[atrule] || ctx.theme.breakpoints[atrule.substring(1)])}`; } let { groups: { at, rule } } = atrule.match(/^@(?<at>media|c|container|l|layer|s|supports)?(?<rule>\S+)$/); // Map @rule shortcuts like @c, @l, @s, those are different from theme breakpoints as they have a value! // => @l(<layer-name>), @s(display=grid), @c(width>618px) at = { c: 'container', l: 'layer', s: 'supports', }[at] || at; const operators = { '&&': () => ') and (', '||': () => '), (', '!': () => '), not all and (', '<': (p, inv) => (!inv ? `max-${p}: ` : `min-${p}: `), '<=': (p, inv) => (!inv ? `max-${p}: ` : `min-${p}: `), '=': () => ':', '>=': (p, inv) => (!inv ? `min-${p}: ` : `max-${p}: `), '>': (p, inv) => (!inv ? `min-${p}: ` : `max-${p}: `), }; let invert = false; let property = 'width'; // Unwarp @rule from parenthesis if it comes in wrapped rule = (rule.startsWith('(') ? rule.substring(1, rule.length-1) : rule); // Deal with simple values with no operators like @320px, prefix so it transforms to `max-width` // this allows something like `bg:red@320` to translate to `@media (max-width: 320px)` rule = rule.replace(/^(?<v>[\d.]+)(?<u>[a-z%]{1,4})?$/, (m, v, u) => `<${v}${u || 'px'}`); // Deal with range syntax, anything else won't be matched, transform into CSS3 conditions // don't check for proper units here, stop being lazy and write them... rule = rule.replace(/(?<left>[^<>=\s]+)?(?<oleft><=|>=|=|<|>)?(?<prop>width|height)(?<oright><=|>=|=|<|>)(?<right>[^<>=\s]+)/, (match, left, oleft, prop, oright, right) => { invert = !!oleft; property = prop; return `${oleft||''}${left||''}${oleft ? '&&' : ''}${oright}${right}`; }); // Use `.split()` with a RegEx and a capturing group to include the separators const parts = rule.split(/([^<>=&|!]+)?(&&|\|\||!|<=|>=|=|<|>)([^<>=&|!]+)/).filter(x => x); const result = parts.reduce((r, v, i, arr) => { if ( operators[v] ) { r.push(operators[v](property, invert)); // Only applies to range syntax, we have to invert the first operator, then reset the var invert = false; } else { // Resolve custom CSS properties/variables, we can't use them in @rules // a bit like https://github.com/WolfgangKluge/postcss-media-variables if ( v.startsWith('--') ) { v = getComputedStyle(document.documentElement).getPropertyValue(v).trim() || `${v}__var-undefined`; } // Cast unitless numeric values to pixels r.push((/^[\d\.]+$/.test(v) ? `${v}px` : v)); } // Add closing parenthesis if we reached the end (i == arr.length-1 && !(at || '').includes('layer')) && r.push(')'); return r; }, ['@', (at || 'media'), `${!(at || '').includes('layer') ? ' (' : ' '}`]).join(''); //console.log(result); return result; } return { // slice @rule and pass to the next variants and rules matcher: matcher.replace(atrule, ''), //selector: (s) => s, handle: (input, next) => { //console.log('input', input, 'next', next); return next({ ...input, parent: `${input.parent ? `${input.parent} $$ ` : ''}${transform(atrule)}`, }) }, } }, multipass: true, }, // Adds !important to a rules resulting CSS if the class is prefixed with !, e.g. `!t:red` { name: 'important', match: (matcher, ctx) => { if ( !matcher.startsWith('!') ) { return matcher; } return { matcher: matcher.slice(1), // body is an array of tuples like `[ [<prop>, <value>], [<prop>, <value>] ]` body: (body) => { body.forEach(([prop, val], i, arr) => { arr[i] = [prop, ( val ? val += ' !important' : val )]; }); return body; }, } }, multipass: false, }, ], preflights: [ { layer: 'recss', //getCSS: async () => (await fetch('my-style.css')).text(), // `:` (colon) needs double escaping when used in template literal! e.g. `<foo>\\:<bar>` getCSS: ({ theme }) => ` /** /* Little pseudo content helpers. /*/ [data-before]:before { content: attr(data-before); } [data-after]:after { content: attr(data-after); } [data-class]:after { content: attr(class); } /** /* Yes, invalid attribute, but no browser cares, useful for easily showing/hiding entire blocks /* based on FreeMarker/JS conditions. Use like <div class="..." if="{somebooleanexpression?c}">. /*/ [if="false"] { display: none !important; } /** /* Use for elements that should only be visible when handled by JavaScript, it's the JS code's /* responsibility to remove this class once it has done whatever it's doing. Useful for /* pre-rendering markup in FreeMarker and then progressively enhance it with JS. /*/ .js--only { display: none !important; } /** TODO: IMPLEMENT THESE DYNAMICALLY! **/ /** /* LAYOUT, DISPLAY & POSITIONING UTILS /* 1. Completely remove from the flow but leave available to screen readers. /* 2. Fix for Firefox bug: an image styled 'max-width:100%' within an /* inline-block will display at its default size, and not limit its width to /* 100% of an ancestral container. /* 3. The space content is one way to avoid an Opera bug when the /* 'contenteditable' attribute is included anywhere else in the document. /* Otherwise it causes space to appear at the top and bottom of the /* element. /* 4. The use of 'table' rather than 'block' is only necessary if using /* ':before' to contain the top-margins of child elements. /* 5. Make sure fixed elements are promoted into a new layer, for performance /* reasons. /* 6. Element will be absolutely centered inside the nearest relatively-positioned ancestor. /* 7. Element will be centered horizontally regardless of width. Setting 'transform: perspective(1px)' /* prevents element from being blurry if positioned on a "half-pixel", alternatively, setting /* 'transform-style: preserve-3d;' on the parent element has the same effect! /* 8. Element will be centered vertically regardless of height. Setting 'transform: perspective(1px)' /* prevents element from being blurry if positioned on a "half-pixel", alternatively, setting /* 'transform-style: preserve-3d;' on the parent element has the same effect! /* 9. Fix for Chrome 44 bug. https://code.google.com/p/chromium/issues/detail?id=506893 /* 10. Setting percentage height is rather rare, not worth the bloat of all utility classes /*/ .d\\:ib-fix { font-size: 0; line-height: 0; } .d\\:ib-fix > *, .d\\:ib-fix *:before, .d\\:ib-fix *:after { font-size: initial; line-height: initial; vertical-align: middle; } .d\\:f-col-r { -webkit-box-orient: vertical; -webkit-box-direction: reverse; -webkit-flex-direction: column-reverse; -ms-flex-direction: column-reverse; flex-direction: column-reverse; } .d\\:f-row-r { -webkit-box-orient: horizontal; -webkit-box-direction: reverse; -webkit-flex-direction: row-reverse; -ms-flex-direction: row-reverse; flex-direction: row-reverse; } .d\\:f-wrap-r { -webkit-flex-wrap: wrap-reverse; -ms-flex-wrap: wrap-reverse; flex-wrap: wrap-reverse; } .d\\:fc-items-start { -webkit-box-align: start; -webkit-align-items: flex-start; -ms-flex-align: start; align-items: flex-start; } .d\\:fc-items-end { -webkit-box-align: end; -webkit-align-items: flex-end; -ms-flex-align: end; align-items: flex-end; } .d\\:fc-items-baseline { -webkit-box-align: baseline; -webkit-align-items: baseline; -ms-flex-align: baseline; align-items: baseline; } .d\\:fc-items-stretch { -webkit-box-align: stretch; -webkit-align-items: stretch; -ms-flex-align: stretch; align-items: stretch; } .d\\:fc-justify-start { -webkit-box-pack: start; -webkit-justify-content: flex-start; -ms-flex-pack: start; justify-content: flex-start; } .d\\:fc-justify-end { -webkit-box-pack: end; -webkit-justify-content: flex-end; -ms-flex-pack: end; justify-content: flex-end; } .d\\:fc-justify-around { -webkit-justify-content: space-around; -ms-flex-pack: distribute; justify-content: space-around; } .d\\:fc-justify-evenly { -webkit-box-pack: space-evenly; -webkit-justify-content: space-evenly; -ms-flex-pack: space-evenly; justify-content: space-evenly; /* not supported in Edge! */ } .d\\:fc-content-start { -webkit-align-content: flex-start; -ms-flex-line-pack: start; align-content: flex-start; } .d\\:fc-content-end { -webkit-align-content: flex-end; -ms-flex-line-pack: end; align-content: flex-end; } .d\\:fc-content-center { -webkit-align-content: center; -ms-flex-line-pack: center; align-content: center; } .d\\:fc-content-between { -webkit-align-content: space-between; -ms-flex-line-pack: justify; align-content: space-between; } .d\\:fc-content-around { -webkit-align-content: space-around; -ms-flex-line-pack: distribute; align-content: space-around; } .d\\:fc-content-stretch { -webkit-align-content: stretch; -ms-flex-line-pack: stretch; align-content: stretch; } .d\\:fi-self-start { -webkit-align-self: flex-start; -ms-flex-item-align: start; align-self: flex-start; } .d\\:fi-self-end { -webkit-align-self: flex-end; -ms-flex-item-align: end; align-self: flex-end; } .d\\:fi-self-center { -webkit-align-self: center; -ms-flex-item-align: center; align-self: center; } .d\\:fi-self-baseline { -webkit-align-self: baseline; -ms-flex-item-align: baseline; align-self: baseline; } .d\\:fi-self-stretch { -webkit-align-self: stretch; -ms-flex-item-align: stretch; align-self: stretch; } .d\\:fi-grow { -webkit-box-flex: 1; -webkit-flex: 1 1 auto; -ms-flex: 1 1 auto; flex: 1 1 auto; min-width: 0; /* 9 */ min-height: 0; /* 9 */ } .d\\:table { display: table; } .d\\:table-collapse { border-collapse: collapse; } .d\\:table-separate { border-collapse: separate; } .d\\:table-spacing { border-spacing: 1px; } .d\\:td { display: table-cell; } .d\\:tr { display: table-row; } .d\\:left { float: left; } .d\\:right { float: right; } .d\\:clear:before, .d\\:clear:after { content: " "; /* 3 */ display: table; /* 4 */ } .d\\:clear:after { clear: both; } .d\\:valign-baseline { vertical-align: baseline; } .d\\:valign-top { vertical-align: top; } .d\\:valign-middle { vertical-align: middle; } .d\\:valign-bottom { vertical-align: bottom; } .d\\:valign-all-baseline > * { vertical-align: baseline; } .d\\:valign-all-top > * { vertical-align: top; } .d\\:valign-all-middle > * { vertical-align: middle; } .d\\:valign-all-bottom > * { vertical-align: bottom; } /** /* Skeleton loader styles /* @credits: https://css-tricks.com/a-bare-bones-approach-to-versatile-and-reusable-skeleton-loaders/ /* Multi-row skeleton loaders can be added by adding <br/> elements assuming that all /* child (content) elements get replaced with actual content once loading is done. /*/ .is--loading, .is--loading * { pointer-events: none; user-select: none; cursor: default; } .is--loading .sk\\:el { animation: 2s sk\\:progress linear infinite; background-size: 200% 100%; /* for loading animation */ background: var(--c-skeleton-bg); background: var(--c-skeleton-gradient); border-color: rgba(0,0,0,0) !important; border-radius: 2px; color: rgba(0,0,0,0) !important; } .is--loading .sk\\:el:is(.sk\\:static) { animation: none !important; background: var(--c-skeleton-bg); } @media (prefers-reduced-motion) { .is--loading .sk\\:el { animation: none !important; background: var(--c-skeleton-bg); } } /* Make sure all child elements are hidden, but preserve their dimensions and layout */ .is--loading .sk\\:el * { visibility: hidden; } /** * Make sure that an element has at least a whitespace character as a child so it displays * properly. This is useful when no text placeholder is present (element is empty). */ .is--loading .sk\\:el:empty:after, .is--loading .sk\\:el *:empty:after { content: ' '; /* Can't use 00a0 (octal escape sequences) in template literls! */ } @keyframes sk\\:progress { to { background-position-x: -200%; } } ` }, ], /*transformers: [ { name: 'my-transformer', enforce: 'pre', // enforce before other transformers idFilter(id) { // only transform .tsx and .jsx files //return id.match(/\.[tj]sx$/) //console.log('transformer: idFilter', id); return true; }, async transform(code, id, { uno }) { // code is a MagicString instance //console.log('transformer: transform()', code, id, uno); }, } ],*/ /* preprocess: (t) => { // for example prefix all classes with ! which makes them !important (bad idea!) if (t.includes('!')) return t; return `!${t}`; }, */ // convert pixels to rem for all utils postprocess: (util, ...args) => { if ( !window || !window.__unocss?.theme?.px2rem ) { return } const px = /(-?[\.\d]+)px/g; const reminpx = parseFloat(getComputedStyle(document.documentElement).fontSize); util.entries.forEach((i) => { const value = i[1]; if ( typeof value === 'string' && px.test(value) ) { i[1] = value.replace(px, (_, v) => `${v / reminpx}rem`); } }); }, /*extractors: [ // This is the default split extractor of UnoCSS, comment out what is below if you encounter // issues and try this one, it's very crude... but works with more or less false positives // depending on the markup your are working with { name: '@unocss/core/extractor-split', order: 0, extract({ code }) { const defaultSplitRE = /[\\:]?[\s'"`;{}]+/g; function splitCode(code) { return code.split(defaultSplitRE); }; console.log('split extractor code', typeof code, code.split(defaultSplitRE)); return splitCode(code) }, }, { name: 're:css', order: 0, extract({ code }) { const classes = []; for ([_, q, c] of code.matchAll(/(?:class\s*?=\s*?)(["'])((?:(?=(?:\\)*)\\.|.)*?)\1/gi)) { classes.push(...c.split(/\s+/g)); } console.log('re:css extractor classes', classes); return classes; }, } ],*/ // disable the default extractor //extractorDefault: false, // override the default extractor // This one WILL FAIL if you do not quote your class attribute values (as you should anyways)! // But this extractor is 5-10x faster than the default extractor extractorDefault: { name: 're:css', order: 0, extract({ code }) { //const start = performance.now(); const classes = []; for (match of code.matchAll(/(?:class\s*?=\s*?)(["'])((?:(?=(?:\\)*)\\.|.)*?)\1/gi)) { // we use the default splitter RegEx, but only to split class attribute values, nothing else // it properly deals with inline riot `<template>` tags that contain unparsed expressions // `{ <expression> }` which cause problems with a 'simple' whitespace splitter... classes.push(...match[2].split(/[\\:]?[\s'"`;{}]+/g)); } //console.log('re:css extractor classes', classes); //console.log(`custom extractor done in ${performance.now()-start}ms`); return classes; }, }, // This is actually the default extractor, takes 60ms+ for a large HTML document /*extractorDefault: { name: '@unocss/core/extractor-split', order: 0, extract({ code }) { //const start = performance.now(); const defaultSplitRE = /[\\:]?[\s'"`;{}]+/g; function splitCode(code) { return code.split(defaultSplitRE); }; const tokens = splitCode(code); //console.log(`default extractor done in ${performance.now()-start}ms`); return tokens; }, },*/ runtime: { inject: (styleElement) => document.head.append(styleElement), observer: { target: () => document.querySelector('.lia-page'), attributeFilter: ['class'], }, //inspect: (el) => { console.log(el); if ( /\S+:\S+/gi.test(el.getAttribute('class') || '') ) { console.log('uno inspect', el, el.classList, el.classList.matchAll(/\S+:\S+/gi)); } return true; }, ready: (ctx) => { //console.log('uno ready?', `${performance.now()-window.$start}ms`, ctx); // we can't pass the inspect callback directly via runtime config? why? ctx.inspect((el) => { if ( /\S+:\S+/gi.test(el.getAttribute('class') || '') ) { [...el.classList].filter((x) => /\S+:\S+/i.test(x)).forEach((v) => { window.$stats[v] = (window.$stats[v] || 0) + 1; }); } // need to return true from inspector callback return true; }); // need to return true from ready return true; }, //configResolved: (config, defaults) => { console.log('uno config resolved, modify it?', `${performance.now()-window.$start}ms`, config, defaults); }, }, }; </script> <script src="https://community.hubspot.com/html/@BAA2A9A38B8DF4DC5426B3D61241E9AB/assets/_core.libs.min.js" type="text/javascript"></script> <script src="https://community.hubspot.com/html/@B5A089E4D43239236FCE70D92814096D/assets/_core.global.min.js" type="text/javascript"></script> <script src="https://community.hubspot.com/html/@F771820AC374A1A1436A3D822BE61093/assets/_cmp.min.js" type="text/javascript"></script> <link rel="icon" href="https://community.hubspot.com/html/@46292D292824DF071B6641C2DA6FDD8E/assets/favicon.png"> <!--[if IE]><link rel="shortcut icon" href="https://community.hubspot.com/html/@46292D292824DF071B6641C2DA6FDD8E/assets/favicon.png"><![endif]--> <meta class="swiftype" name="doc-type" data-type="string" content="Community"> <script data-external-hs-domain="true" data-gtm-id="GTM-M3KWR2J" src="https://www.hubspot.com/wt-assets/static-files/compliance/index.js" defer nonce></script> <script type="text/javascript" src="/t5/scripts/2410E039424F69BAC69F910CA12BBD87/lia-scripts-head-min.js"></script><script language="javascript" type="text/javascript"> <!-- window.FileAPI = { jsPath: '/html/assets/js/vendor/ng-file-upload-shim/' }; LITHIUM.PrefetchData = {"Components":{},"commonResults":{}}; LITHIUM.DEBUG = false; LITHIUM.CommunityJsonObject = { "Validation" : { "image.description" : { "min" : 0, "max" : 1000, "isoneof" : [ ], "type" : "string" }, "tkb.toc_maximum_heading_level" : { "min" : 1, "max" : 6, "isoneof" : [ ], "type" : "integer" }, "tkb.toc_heading_list_style" : { "min" : 0, "max" : 50, "isoneof" : [ "disc", "circle", "square", "none" ], "type" : "string" }, "blog.toc_maximum_heading_level" : { "min" : 1, "max" : 6, "isoneof" : [ ], "type" : "integer" }, "tkb.toc_heading_indent" : { "min" : 5, "max" : 50, "isoneof" : [ ], "type" : "integer" }, "blog.toc_heading_indent" : { "min" : 5, "max" : 50, "isoneof" : [ ], "type" : "integer" }, "blog.toc_heading_list_style" : { "min" : 0, "max" : 50, "isoneof" : [ "disc", "circle", "square", "none" ], "type" : "string" } }, "User" : { "settings" : { "imageupload.legal_file_extensions" : "*.jpg;*.JPG;*.jpeg;*.JPEG;*.gif;*.GIF;*.png;*.PNG", "config.enable_avatar" : true, "integratedprofile.show_klout_score" : true, "layout.sort_view_by_last_post_date" : false, "layout.friendly_dates_enabled" : true, "profileplus.allow.anonymous.scorebox" : false, "tkb.message_sort_default" : "topicPublishDate", "layout.format_pattern_date" : "MMM d, yyyy", "config.require_search_before_post" : "off", "isUserLinked" : false, "integratedprofile.cta_add_topics_dismissal_timestamp" : -1, "layout.message_body_image_max_size" : 1000, "profileplus.everyone" : false, "integratedprofile.cta_connect_wide_dismissal_timestamp" : -1, "blog.toc_maximum_heading_level" : "", "integratedprofile.hide_social_networks" : false, "blog.toc_heading_indent" : "", "contest.entries_per_page_num" : 20, "layout.messages_per_page_linear" : 12, "integratedprofile.cta_manage_topics_dismissal_timestamp" : -1, "profile.shared_profile_test_group" : false, "integratedprofile.cta_personalized_feed_dismissal_timestamp" : -1, "integratedprofile.curated_feed_size" : 10, "contest.one_kudo_per_contest" : false, "integratedprofile.enable_social_networks" : false, "integratedprofile.my_interests_dismissal_timestamp" : -1, "profile.language" : "en", "layout.friendly_dates_max_age_days" : 31, "layout.threading_order" : "thread_descending", "blog.toc_heading_list_style" : "disc", "useRecService" : false, "layout.module_welcome" : "", "imageupload.max_uploaded_images_per_upload" : 100, "imageupload.max_uploaded_images_per_user" : 6000, "integratedprofile.connect_mode" : "", "tkb.toc_maximum_heading_level" : "", "tkb.toc_heading_list_style" : "disc", "sharedprofile.show_hovercard_score" : true, "config.search_before_post_scope" : "container", "tkb.toc_heading_indent" : "", "p13n.cta.recommendations_feed_dismissal_timestamp" : -1, "imageupload.max_file_size" : 3072, "layout.show_batch_checkboxes" : false, "integratedprofile.cta_connect_slim_dismissal_timestamp" : -1 }, "isAnonymous" : true, "policies" : { "image-upload.process-and-remove-exif-metadata" : false }, "registered" : false, "emailRef" : "", "id" : -1, "login" : "Anonymous" }, "Server" : { "communityPrefix" : "/mjmao93648", "nodeChangeTimeStamp" : 1732390484891, "tapestryPrefix" : "/t5", "deviceMode" : "DESKTOP", "responsiveDeviceMode" : "DESKTOP", "membershipChangeTimeStamp" : "0", "version" : "24.8", "branch" : "24.8-release", "showTextKeys" : false }, "Config" : { "phase" : "prod", "integratedprofile.cta.reprompt.delay" : 30, "profileplus.tracking" : { "profileplus.tracking.enable" : false, "profileplus.tracking.click.enable" : false, "profileplus.tracking.impression.enable" : false }, "app.revision" : "2410251442-s96644fcabc-b95", "navigation.manager.community.structure.limit" : "1000" }, "Activity" : { "Results" : [ { "name" : "UserUpdated", "user" : { "uid" : -1, "login" : "Anonymous" } } ] }, "NodeContainer" : { "viewHref" : "https://community.hubspot.com/t5/Advocacy/ct-p/advocacy", "description" : "HubSpot Advocacy", "id" : "advocacy", "shortTitle" : "Advocacy", "title" : "Advocacy", "nodeType" : "category" }, "Page" : { "skins" : [ "hubspot", "responsive_peak" ], "authUrls" : { "loginUrl" : "https://app.hubspot.com/khoros/integration/jwt/authenticate?referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2FCommunity-Champion-Opportunities%2FShare-your-review-of-Adobe-Express%2Fba-p%2F1062774", "loginUrlNotRegistered" : "https://app.hubspot.com/khoros/integration/jwt/authenticate?redirectreason=notregistered&referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2FCommunity-Champion-Opportunities%2FShare-your-review-of-Adobe-Express%2Fba-p%2F1062774", "loginUrlNotRegisteredDestTpl" : "https://app.hubspot.com/khoros/integration/jwt/authenticate?redirectreason=notregistered&referer=%7B%7BdestUrl%7D%7D" }, "name" : "BlogArticlePage", "rtl" : false, "object" : { "viewHref" : "/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774", "subject" : "Share your review of Adobe Express", "id" : 1062774, "page" : "BlogArticlePage", "type" : "Thread" } }, "WebTracking" : { "Activities" : { }, "path" : "Community:HubSpot Community/Category:HubSpot Community/Category:Advocacy/Blog:Community Champion Opportunities/Article:Share your review of Adobe Express" }, "Feedback" : { "targeted" : { } }, "Seo" : { "markerEscaping" : { "pathElement" : { "prefix" : "@", "match" : "^[0-9][0-9]$" }, "enabled" : false } }, "TopLevelNode" : { "viewHref" : "https://community.hubspot.com/", "description" : "Welcome to the HubSpot Community! Connect with peers, maximize your HubSpot knowledge, and learn how to grow better with HubSpot.", "id" : "mjmao93648", "shortTitle" : "HubSpot Community", "title" : "HubSpot Community", "nodeType" : "Community" }, "Community" : { "viewHref" : "https://community.hubspot.com/", "integratedprofile.lang_code" : "en", "integratedprofile.country_code" : "US", "id" : "mjmao93648", "shortTitle" : "HubSpot Community", "title" : "HubSpot Community" }, "CoreNode" : { "conversationStyle" : "blog", "viewHref" : "https://community.hubspot.com/t5/Community-Champion-Opportunities/bg-p/community-champion-opportunities", "settings" : { }, "description" : "Engage and complete advocacy opportunities, earn points, and rise through the ranks as a HubSpot Community Champion.", "id" : "community-champion-opportunities", "shortTitle" : "Community Champion Opportunities", "title" : "Community Champion Opportunities", "nodeType" : "Board", "ancestors" : [ { "viewHref" : "https://community.hubspot.com/t5/Advocacy/ct-p/advocacy", "description" : "HubSpot Advocacy", "id" : "advocacy", "shortTitle" : "Advocacy", "title" : "Advocacy", "nodeType" : "category" }, { "viewHref" : "https://community.hubspot.com/t5/HubSpot-Community/ct-p/hubspot_community_en", "description" : "Welcome to the HubSpot Community! Connect with peers, maximize your HubSpot knowledge, and learn how to grow better with HubSpot.", "id" : "hubspot_community_en", "shortTitle" : "HubSpot Community", "title" : "HubSpot Community", "nodeType" : "category" }, { "viewHref" : "https://community.hubspot.com/", "description" : "Welcome to the HubSpot Community! Connect with peers, maximize your HubSpot knowledge, and learn how to grow better with HubSpot.", "id" : "mjmao93648", "shortTitle" : "HubSpot Community", "title" : "HubSpot Community", "nodeType" : "Community" } ] } }; LITHIUM.Components.RENDER_URL = "/t5/util/componentrenderpage/component-id/#{component-id}?render_behavior=raw"; LITHIUM.Components.ORIGINAL_PAGE_NAME = 'blogs/v2/BlogArticlePage'; LITHIUM.Components.ORIGINAL_PAGE_ID = 'BlogArticlePage'; LITHIUM.Components.ORIGINAL_PAGE_CONTEXT = 'cl745FAp_C-MdwO7h8NjritC9fT_n9iWEjAFzHrz8lZr0LE3JepuVyMVWtWjsWDjrKBR4MV2ua-R-VQkCywrmoKjbqNT2CEZVYFjTZFuI-3SjHZl6BZSDDz82VIcHP7IxmBZwjNFV8Tc1eLS8dyEWGDEctTnmLb71_JcCxgGnjy6V4WnNdObMWiHbuurKtu7-nS74p6D-bpqMAgms69Rk_qY7y82gWGJA1rUiTnlCE0NNgxPe1H0qCZGSYspAwTVX_XupV0R5V4d37Lo7sBNhq-_UxQ2wSMUoRuSuLdrOTBOUSGUfYQMOSIZ0no96Tue2iOElhzEZ7qTO5Z-DzSOd0X2PqpG_0MrnXbTYNEEXPnqv4m2tabMeLRGsIOiRDwvNM7VsTRHXO1as-y-9B_R_g..'; LITHIUM.Css = { "BASE_DEFERRED_IMAGE" : "lia-deferred-image", "BASE_BUTTON" : "lia-button", "BASE_SPOILER_CONTAINER" : "lia-spoiler-container", "BASE_TABS_INACTIVE" : "lia-tabs-inactive", "BASE_TABS_ACTIVE" : "lia-tabs-active", "BASE_AJAX_REMOVE_HIGHLIGHT" : "lia-ajax-remove-highlight", "BASE_FEEDBACK_SCROLL_TO" : "lia-feedback-scroll-to", "BASE_FORM_FIELD_VALIDATING" : "lia-form-field-validating", "BASE_FORM_ERROR_TEXT" : "lia-form-error-text", "BASE_FEEDBACK_INLINE_ALERT" : "lia-panel-feedback-inline-alert", "BASE_BUTTON_OVERLAY" : "lia-button-overlay", "BASE_TABS_STANDARD" : "lia-tabs-standard", "BASE_AJAX_INDETERMINATE_LOADER_BAR" : "lia-ajax-indeterminate-loader-bar", "BASE_AJAX_SUCCESS_HIGHLIGHT" : "lia-ajax-success-highlight", "BASE_CONTENT" : "lia-content", "BASE_JS_HIDDEN" : "lia-js-hidden", "BASE_AJAX_LOADER_CONTENT_OVERLAY" : "lia-ajax-loader-content-overlay", "BASE_FORM_FIELD_SUCCESS" : "lia-form-field-success", "BASE_FORM_WARNING_TEXT" : "lia-form-warning-text", "BASE_FORM_FIELDSET_CONTENT_WRAPPER" : "lia-form-fieldset-content-wrapper", "BASE_AJAX_LOADER_OVERLAY_TYPE" : "lia-ajax-overlay-loader", "BASE_FORM_FIELD_ERROR" : "lia-form-field-error", "BASE_SPOILER_CONTENT" : "lia-spoiler-content", "BASE_FORM_SUBMITTING" : "lia-form-submitting", "BASE_EFFECT_HIGHLIGHT_START" : "lia-effect-highlight-start", "BASE_FORM_FIELD_ERROR_NO_FOCUS" : "lia-form-field-error-no-focus", "BASE_EFFECT_HIGHLIGHT_END" : "lia-effect-highlight-end", "BASE_SPOILER_LINK" : "lia-spoiler-link", "BASE_DISABLED" : "lia-link-disabled", "FACEBOOK_LOGOUT" : "lia-component-users-action-logout", "FACEBOOK_SWITCH_USER" : "lia-component-admin-action-switch-user", "BASE_FORM_FIELD_WARNING" : "lia-form-field-warning", "BASE_AJAX_LOADER_FEEDBACK" : "lia-ajax-loader-feedback", "BASE_AJAX_LOADER_OVERLAY" : "lia-ajax-loader-overlay", "BASE_LAZY_LOAD" : "lia-lazy-load" }; LITHIUM.noConflict = true; LITHIUM.useCheckOnline = false; LITHIUM.RenderedScripts = [ "Video.js", "PartialRenderProxy.js", "jquery.lithium-selector-extensions.js", "Events.js", "Forms.js", "HelpIcon.js", "ResizeSensor.js", "jquery.css-data-1.0.js", "jquery.tmpl-1.1.1.js", "AutoComplete.js", "json2.js", "Cache.js", "Placeholder.js", "Namespace.js", "jquery.ui.resizable.js", "jquery.iframe-shim-1.0.js", "CustomEvent.js", "Auth.js", "Throttle.js", "jquery.hoverIntent-r6.js", "Lithium.js", "ElementQueries.js", "jquery.ui.widget.js", "ForceLithiumJQuery.js", "jquery.autocomplete.js", "Sandbox.js", "jquery.js", "jquery.json-2.6.0.js", "InformationBox.js", "DataHandler.js", "jquery.function-utils-1.0.js", "jquery.fileupload.js", "jquery.effects.slide.js", "Link.js", "DeferredImages.js", "Text.js", "MessageBodyDisplay.js", "Components.js", "jquery.tools.tooltip-1.2.6.js", "LiModernizr.js", "Globals.js", "jquery.ajax-cache-response-1.0.js", "jquery.ui.draggable.js", "jquery.effects.core.js", "jquery.scrollTo.js", "jquery.ui.mouse.js", "jquery.viewport-1.0.js", "ActiveCast3.js", "jquery.ui.core.js", "prism.js", "jquery.iframe-transport.js", "PolyfillsOld.js", "NoConflict.js", "AjaxSupport.js", "Loader.js", "jquery.clone-position-1.0.js", "PolyfillsAll.js", "Tooltip.js", "jquery.ui.position.js", "AjaxFeedback.js", "SearchForm.js", "jquery.appear-1.1.1.js", "ElementMethods.js", "jquery.delayToggle-1.0.js", "DropDownMenuVisibilityHandler.js", "SpoilerToggle.js", "jquery.ui.dialog.js", "EarlyEventCapture.js", "jquery.position-toggle-1.0.js", "jquery.blockui.js", "DropDownMenu.js", "jquery.placeholder-2.0.7.js", "SearchAutoCompleteToggle.js" ];// --> </script><script type="text/javascript" src="/t5/scripts/D60EB96AE5FF670ED274F16ABB044ABD/lia-scripts-head-min.js"></script></head> <body class="lia-blog lia-user-status-anonymous BlogArticlePage lia-body" id="lia-body"> <div id="997-198-7" class="ServiceNodeInfoHeader"> </div> <div class="lia-page"> <center> <noscript class=" " id="hubspot" data-page="BlogArticlePage" data-style="blog" data-rootid="hubspot_community_en" data-roottype="category" data-topid="hubspot_community_en" data-nodeid="community-champion-opportunities" data-nodetype="board" data-nodelang="en" data-userlang="en" data-skin="hubspot"> <p>JavaScript must be installed and enabled to use these boards.<p> Your browser appears to have JavaScript disabled or does not support JavaScript. Please refer to your browser's help file to determine how to enable JavaScript.</p> </noscript> <svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <symbol id="i:hubspot" viewBox="0 0 32 32"> <path d="M23.7 10.92V7.36a2.74 2.74 0 0 0 1.59-2.48V4.8a2.75 2.75 0 0 0-2.74-2.74h-.08a2.75 2.75 0 0 0-2.74 2.74v.08c0 1.1.64 2.03 1.56 2.47h.02v3.57a7.7 7.7 0 0 0-3.71 1.64l.01-.01-9.78-7.62a3.12 3.12 0 1 0-1.45 1.9h-.01l9.62 7.49a7.75 7.75 0 0 0 .14 8.81l-.02-.03-2.93 2.94a2.54 2.54 0 1 0 1.81 2.43c0-.27-.04-.52-.12-.75v.01l2.9-2.9a7.8 7.8 0 1 0 5.98-13.91h-.04zm-1.2 11.72a3.55 3.55 0 1 1 .01 0z" /> </symbol> <symbol id="i:link" viewBox="0 0 20 20"> <path d="M7.859 14.691l-0.81 0.805c-0.701 0.695-1.843 0.695-2.545 0-0.336-0.334-0.521-0.779-0.521-1.252s0.186-0.916 0.521-1.252l2.98-2.955c0.617-0.613 1.779-1.515 2.626-0.675 0.389 0.386 1.016 0.384 1.403-0.005 0.385-0.389 0.383-1.017-0.006-1.402-1.438-1.428-3.566-1.164-5.419 0.675l-2.98 2.956c-0.715 0.709-1.108 1.654-1.108 2.658 0 1.006 0.394 1.949 1.108 2.658 0.736 0.73 1.702 1.096 2.669 1.096s1.934-0.365 2.669-1.096l0.811-0.805c0.389-0.385 0.391-1.012 0.005-1.4s-1.014-0.39-1.403-0.006zM16.891 3.207c-1.547-1.534-3.709-1.617-5.139-0.197l-1.009 1.002c-0.389 0.386-0.392 1.013-0.006 1.401 0.386 0.389 1.013 0.391 1.402 0.005l1.010-1.001c0.74-0.736 1.711-0.431 2.346 0.197 0.336 0.335 0.522 0.779 0.522 1.252s-0.186 0.917-0.522 1.251l-3.18 3.154c-1.454 1.441-2.136 0.766-2.427 0.477-0.389-0.386-1.016-0.383-1.401 0.005s-0.384 1.017 0.005 1.401c0.668 0.662 1.43 0.99 2.228 0.99 0.977 0 2.010-0.492 2.993-1.467l3.18-3.153c0.712-0.71 1.107-1.654 1.107-2.658s-0.395-1.949-1.109-2.659z"></path> </symbol> <symbol id="i:linkedin" viewBox="0 0 20 20"> <path d="M10 0.4c-5.302 0-9.6 4.298-9.6 9.6s4.298 9.6 9.6 9.6 9.6-4.298 9.6-9.6-4.298-9.6-9.6-9.6zM7.65 13.979h-1.944v-6.256h1.944v6.256zM6.666 6.955c-0.614 0-1.011-0.435-1.011-0.973 0-0.549 0.409-0.971 1.036-0.971s1.011 0.422 1.023 0.971c0 0.538-0.396 0.973-1.048 0.973zM14.75 13.979h-1.944v-3.467c0-0.807-0.282-1.355-0.985-1.355-0.537 0-0.856 0.371-0.997 0.728-0.052 0.127-0.065 0.307-0.065 0.486v3.607h-1.945v-4.26c0-0.781-0.025-1.434-0.051-1.996h1.689l0.089 0.869h0.039c0.256-0.408 0.883-1.010 1.932-1.010 1.279 0 2.238 0.857 2.238 2.699v3.699z"></path> </symbol> <symbol id="i:xcom" viewBox="0 0 32 32"> <path d="M24.325 3h4.411l-9.636 11.013 11.336 14.987h-8.876l-6.952-9.089-7.955 9.089h-4.413l10.307-11.78-10.875-14.22h9.101l6.284 8.308zM22.777 26.36h2.444l-15.776-20.859h-2.623z"></path> </symbol> <symbol id="i:connectcom" viewBox="0 0 24 24"> <path fill="#192733" style="fill: var(--color1, #192733)" d="M17.585 5.753c-1.941 0-3.675 0.886-4.822 2.273l0.005 0.005c-0.025 0.030-0.052 0.060-0.075 0.092l-3.988 5.512c-0.608 0.75-1.335 1.171-2.291 1.171-1.549 0-2.806-1.258-2.806-2.806s1.258-2.806 2.806-2.806c1.082 0 1.953 0.603 2.519 1.492l2.261-2.712c-1.146-1.36-2.861-2.221-4.78-2.221-3.452 0.003-6.248 2.799-6.248 6.248s2.797 6.248 6.248 6.248c1.941 0 3.675-0.886 4.822-2.273l-0.005-0.005c0.067-0.067 0.132-0.136 0.189-0.216l4.023-5.559c0.579-0.642 1.263-1 2.142-1 1.549 0 2.806 1.258 2.806 2.806s-1.258 2.806-2.806 2.806c-1.082 0-1.953-0.603-2.519-1.492l-2.261 2.712c1.146 1.36 2.861 2.221 4.78 2.221 3.452 0 6.248-2.797 6.248-6.248s-2.797-6.248-6.248-6.248z"></path> <path fill="#ff5c35" style="fill: var(--color2, #ff5c35)" d="M17.585 10.15c-0.747 0-1.389 0.446-1.682 1.085h-7.737c-0.293-0.64-0.935-1.085-1.682-1.085-1.020 0-1.849 0.829-1.849 1.849s0.829 1.849 1.849 1.849c0.747 0 1.389-0.446 1.682-1.085h7.737c0.293 0.64 0.935 1.085 1.682 1.085 1.020 0 1.849-0.829 1.849-1.849s-0.829-1.849-1.849-1.849z"></path> </symbol> <symbol id="i:globe" viewBox="0 0 24 24"> <path d="M16.951 11c-0.214-2.69-1.102-5.353-2.674-7.71 1.57 0.409 2.973 1.232 4.087 2.346 1.408 1.408 2.351 3.278 2.581 5.364zM14.279 20.709c1.483-2.226 2.437-4.853 2.669-7.709h3.997c-0.23 2.086-1.173 3.956-2.581 5.364-1.113 1.113-2.516 1.936-4.085 2.345zM7.049 13c0.214 2.69 1.102 5.353 2.674 7.71-1.57-0.409-2.973-1.232-4.087-2.346-1.408-1.408-2.351-3.278-2.581-5.364zM9.721 3.291c-1.482 2.226-2.436 4.853-2.669 7.709h-3.997c0.23-2.086 1.173-3.956 2.581-5.364 1.114-1.113 2.516-1.936 4.085-2.345zM12.004 1c0 0 0 0 0 0-3.044 0.001-5.794 1.233-7.782 3.222-1.99 1.989-3.222 4.741-3.222 7.778s1.232 5.789 3.222 7.778c1.988 1.989 4.738 3.221 7.774 3.222 0 0 0 0 0 0 3.044-0.001 5.793-1.233 7.782-3.222 1.99-1.989 3.222-4.741 3.222-7.778s-1.232-5.789-3.222-7.778c-1.988-1.989-4.738-3.221-7.774-3.222zM14.946 13c-0.252 2.788-1.316 5.36-2.945 7.451-1.729-2.221-2.706-4.818-2.945-7.451zM11.999 3.549c1.729 2.221 2.706 4.818 2.945 7.451h-5.89c0.252-2.788 1.316-5.36 2.945-7.451z"></path> </symbol> </defs> </svg> <style class="core-cmp-icons/core" type="text/css"> .i { display: inline-block; fill: currentColor; height: 1rem; width: 1rem; stroke-width: 0; stroke: currentColor; } /* Single-colored icons can be modified like so but this usually happens directly where they are used: */ /* .i\:<name> { font-size: 32px; color: red; } */ </style> <style class="cmp-global-styles/core" type="text/css"> /** /* TODO: Potentially move to skin or leave here? Might be easier to find changes done by us this way? /*/ /* Center category banner card icons (often the HTMl is CC23 based), for example Advocacy */ #lia-body .custom-home-banner-section__cards .card-item__icon { align-self: center; } /* Fix unstyled layout for NotifyModeratorPage quilt */ .lia-quilt-notify-moderator-page > .lia-quilt-row-header .lia-page-header, .lia-quilt-notify-moderator-page > .lia-quilt-row-main { margin: 0 auto; max-width: 1236px; padding-left: 15px; padding-right: 15px; } /* KBCOM-2818: Add node description the hacky way */ .CategoryPage .custom-v2-banner__wrapper .page-title-wrapper:after, .ForumPage .custom-v2-banner__wrapper .page-title-wrapper:after, .GroupHubPage .custom-v2-banner__wrapper .page-title-wrapper:after { content: "Engage and complete advocacy opportunities, earn points, and rise through the ranks as a HubSpot Community Champion."; display: block; margin-bottom: -24px; } /* KBCOM-2802: Fix BlogDashboardPage filter alignment issue */ .BlogDashboardPage .dashboard-wrapper { margin: 0 auto; max-width: 1236px; } .BlogDashboardPage .dashboard-wrapper .lia-node-selector-dropdown { left: auto !important; right: 0; } .BlogDashboardPage .dashboard-wrapper .lia-component-blog-dashboard-tabs ul.lia-tabs-standard, .BlogDashboardPage .dashboard-wrapper .lia-component-blog-widget-dashboard-tabs ul.lia-tabs-standard { padding-left: 0 !important; } /* KBCOM-2831: Remove advanced search options toggle on SearchPage */ .SearchPage .lia-advanced-search-toggle { display: none; } /* re-css cloaking class to hide components relying on dynamically generated classes to only show when they are ready */ [un-cloak] { display: none; } /** /* Custom styles for core feedback elements. Those are usually only used to provide info and hints /* to priviledged roles when issues occur within custom components, so they are not part of the /* regular community theme. /*/ .admininfo { --b-radius: 3px; --H: 0; --S: 0%; --L: 41%; --A: 1; background: hsla(var(--H), var(--S), var(--L), var(--A)); border-radius: var(--b-radius); color: white; display: flex; align-items: center; gap: 24px; /*filter: grayscale(1);*/ font-size: 12px; line-height: 1.25; margin: 24px 0; padding: 24px; transition: all 236ms ease; } .admininfo .checkmark { display: none; } .admininfo__title { display: block; font-weight: bold; font-size: 125%; } .admininfo code { background: white; border-radius: var(--b-radius); color: hsla(var(--H), var(--S), var(--L), 1); display: inline-block; font-size: 90%; font-weight: bold; padding: 2px 4px; } .admininfo a { color: white; display: inline-block; position: relative; } .admininfo a:hover, .admininfo a:focus { text-decoration: none; } .admininfo a:before, .admininfo a:after { border-bottom: 1px dotted white; content: ''; position: absolute; left: 0; right: 0; bottom: -1px; transition: all 382ms ease; } .admininfo a:after { border-bottom: 1px solid white; max-width: 0; width: 0; right: auto; } .admininfo a:hover:after, .admininfo a:focus:after { max-width: 100%; width: 1200px; } .admininfo a:active:after { transform: scaleX(1.0618); } .admininfo.is--error { --S: 100%; --L: 41%; background: hsla(var(--H), var(--S), var(--L), var(--A)); filter: grayscale(0); } .admininfo.is--error .checkmark { display: block; } .admininfo.is--error code { background: #ff4444; color: white; } .admininfo.is--success { --H: 135; --S: 100%; background: hsla(var(--H), var(--S), var(--L), var(--A)); filter: grayscale(0); } .admininfo.is--success code { background: #00c851; color: white; } .checkmark { flex-shrink: 0; /* prevents shrinking under 48px! */ min-width: 48px; width: 48px; height: 48px; border-radius: 50%; display: block; stroke-width: 3px; stroke: white; stroke-miterlimit: 10; } .checkmark_circle_error { stroke-dasharray: 166; stroke-dashoffset: 166; stroke-width: 5px; stroke-miterlimit: 10; stroke: #ff4444; animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards; } .checkmark.error { box-shadow: inset 0px 0px 0px #ff4444; animation: fillerror 0.4s ease-in-out 0.4s forwards, scale 0.3s ease-in-out 0.9s both; } .checkmark_check { transform-origin: 50% 50%; stroke-dasharray: 48; stroke-dashoffset: 48; animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.9s forwards; } .progress { position: absolute; top: 5%; left: 5%; stroke: black; transform: rotate(-90deg); } .progress.progress--thin { left: auto; right: 5%; } .progress circle { stroke-dasharray: 130; stroke-dashoffset: 130; animation: dash 1.5s infinite; } @keyframes dash { 50% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: -130; } } @keyframes stroke { 100% { stroke-dashoffset: 0; } } @keyframes scale { 0%, 100% { transform: none; } 50% { transform: scale3d(1.1, 1.1, 1); } } @keyframes fillerror { 100% { box-shadow: inset 0px 0px 0px 75px #ff4444; } } </style><style class="cmp-global-search-external/core" type="text/css"> @media (min-width: 799px) { .lia-search-input-message + .lia-autocomplete-container { width: 200% !important; max-width: 768px !important; } } .lia-search-input-message + .lia-autocomplete-container .lia-autocomplete-content { display: flex; margin: 0; } @media (max-width: 799px) { .mobile-header form.SerachForm [name="messageSearchField"] + .lia-autocomplete-container > ul:first-of-type { max-height: 210px !important; } /*.mobile-header form.SerachForm [name="messageSearchField"] + .lia-autocomplete-container .collapse-results.fa-chevron-down + ul { max-height: 36px !important; overflow: hidden !important; }*/ .lia-search-input-message + .lia-autocomplete-container .lia-autocomplete-content { flex-direction: column; } } .lia-search-input-message + .lia-autocomplete-container .lia-autocomplete-content>ul>li { padding: 8px 15px 8px 15px; } /* default tag styles */ .SearchPage .search-external-link:after, .is--tag { background-color: var(--color-calypso-light); border: 1px solid transparent; border-radius: 2px; color: var(--color-link-hover); display: inline-block; font-size: 12px; font-weight: 600; line-height: 22px; padding: 0 8px; position: relative; vertical-align: baseline; } .SearchPage .search-external-link:after { line-height: 16px; margin-left: 6px; } /* Override styles form the native Khoros skin... */ .SearchPage .lia-tabs-standard .lia-tabs:first-child { padding-left: 0; } .SearchPage .lia-tabs-standard-wrapper>.lia-tabs-standard { padding: 0 15px !important; /* This one is especially stubborn for some reason I can't explain! */ } </style> <script> // glowingblue: really? xhr.open()? when 5 lines up you were aware you have jQuery? oh boy... function followunfollow(id,value,currentUser,top){ const xhttp = new XMLHttpRequest(); xhttp.onload = function() { if (value=='Unfollow') { document.getElementById("tunfollow-"+id+"").style.display = 'none'; document.getElementById("tfollow-"+id+"").style.display = 'flex'; } if (value=='follow') { document.getElementById("tunfollow-"+id+"").style.display = 'flex'; document.getElementById("tfollow-"+id+"").style.display = 'none'; } } xhttp.open("GET", "/plugins/custom/hubspot/hubspot/follow-unfollow-hover-card-button?id="+id+"&val="+value+"¤tUser="+currentUser+"",true);xhttp.send(); } </script> <div class="MinimumWidthContainer"> <div class="min-width-wrapper"> <div class="min-width"> <div class="lia-content"> <div class="lia-browser-support-alert"> <div class="lia-browser-support-alert-text"> We no longer support Internet Explorer v10 and older, or you have compatibility view enabled. Disable Compatibility view, upgrade to a newer version, or use a different browser. </div> <div class="lia-browser-support-alert-close"> <a class="lia-link-navigation lia-link-ticket-post-action" data-lia-action-token="nUGyx8ntX9R_nwDy2Y70CTMuyHl9XHBbHyOysqgV498." rel="nofollow" id="dismissAlert" href="https://community.hubspot.com/t5/blogs/v2/blogarticlepage.liabase.basebody.browsersupportalert.dismissalert:dismissalert?t:ac=blog-id/community-champion-opportunities/article-id/55"><span class="lia-img-close-small lia-fa-close lia-fa-small lia-fa" title="Dismiss this alert" alt="Dismiss this alert" aria-label="Dismiss this alert" role="img" id="display"></span></a> </div> </div> <div class="lia-quilt lia-quilt-blog-article-page-filtered lia-quilt-layout-one-column lia-top-quilt"> <div class="lia-quilt-row lia-quilt-row-header"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-common-header"> <div class="lia-quilt-column-alley lia-quilt-column-alley-single"> <div class="lia-quilt lia-quilt-header lia-quilt-layout-one-column lia-component-quilt-header"> <div class="lia-quilt-row lia-quilt-row-header"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-common-header"> <div class="lia-quilt-column-alley lia-quilt-column-alley-single"> <!-- hs.custom.responsive-header --> <style> #lia-body .mobile-header .navbar .menu .menu-item.active.has-collapsible .menu-child{ display: flex; flex-direction: column; } .jp-resources-list{ display:flex; flex-direction:column; } .jp-resources-list>li.menu-child-item.jp-class-HubSpot.Community.Blog{ order:1; } .header-dropdown-menu .lia-header-nav-component-widget .private-notes-link:before { content: "Message"; } #lia-body .mobile-header .navbar .menu-wrapper.offcanvas::before {width: 0px;} .profile-menu-dropdown{display: none !important;} .pagination-recent-post.pagination a#jp-previous:after { content: "Previous"; } .pagination-recent-post.pagination a#jp-next:before { content: "Next"; } #lia-body .MessageView.lia-message-view-idea-message-item .lia-quilt-idea-message-item .lia-message-footer-action .lia-link-navigation.lia-message-comment-post:after { content: "0 Comment"; } </style> <div class="header mobile-header"> <nav class="navbar"> <span class="open-menu"> <img src="https://community.hubspot.com/html/@3A38E73C772F7CCD402C2EC02A244F14/assets/Hamburger-Nav.svg"> </span> <span class="hubspot-mobile-logo-wrapper"> <a href="/"> <img src="https://community.hubspot.com/html/@813D252A70F0A7024C8EA3BB1B8B9CFD/assets/sticky-logo.png"> </a> </span> <div class="menu-wrapper"> <div class="menu-block"> <span class="close-menu"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"> <path fill="#252a32" fill-rule="evenodd" d="M17.778.808l1.414 1.414L11.414 10l7.778 7.778-1.414 1.414L10 11.414l-7.778 7.778-1.414-1.414L8.586 10 .808 2.222 2.222.808 10 8.586 17.778.808z" /> </svg> </span> </div> <ul class="menu"> <li class="menu-item has-collapsible"> <a><span></span>Discussions</a> <ul class="menu-child"> <li class="menu-child-item"> <a href="/t5/CRM-Sales-Hub/ct-p/sales" class="nav-dropdown-link nav-discussions-crm"> CRM & Sales </a> </li> <li class="menu-child-item"> <a href="/t5/Marketing-Hub/ct-p/marketing" class="nav-dropdown-link nav-discussions-mktg"> Marketing & Content </a> </li> <li class="menu-child-item"> <a href="/t5/Service-Hub/ct-p/service_hub" class="nav-dropdown-link nav-discussions-svc"> Customer Success & Service </a> </li> <li class="menu-child-item"> <a href="/t5/Operations/ct-p/Operations" class="nav-dropdown-link nav-discussions-ops"> RevOps & Operations </a> </li> <li class="menu-child-item"> <a href="/t5/Commerce/ct-p/commerce" class="nav-dropdown-link nav-discussions-commerce "> Commerce </a> </li> <li class="menu-child-item"> <a href="/t5/HubSpot-Developers/ct-p/developers" class="nav-dropdown-link nav-discussions-developers "> Developers </a> </li> <li class="menu-child-item"> <a href="https://community.hubspot.com/t5/Getting-Started-on-the-Community/How-to-join-the-Solutions-Partner-Program/ba-p/400205" class="nav-dropdown-link nav-partners "> Partners </a> </li> <li class="menu-child-item"> <a href="/t5/HubSpot-Ideas/idb-p/HubSpot_Ideas" class="nav-dropdown-link nav-discussions-ideas"> Ideas </a> </li> </ul> </li> <li class="menu-item has-collapsible"> <a><span></span>Academy</a> <ul class="menu-child"> <li class="nav-dropdown-item"> <a href="https://academy.hubspot.com/courses" class="nav-dropdown-link nav-external-link" target="_blank"> Courses </a> </li> <li class="nav-dropdown-item"> <a href="https://academy.hubspot.com/certification-overview" class="nav-dropdown-link nav-external-link" target="_blank"> Certifications </a> </li> <li class="nav-dropdown-item"> <a href="https://www.hubspot.com/academy/bootcamps/home" class="nav-dropdown-link nav-external-link" target="_blank"> Bootcamps </a> </li> <li class="nav-dropdown-item"> <a href="https://academy.hubspot.com/learning-paths" class="nav-dropdown-link nav-external-link " target="_blank"> Learning Paths </a> </li> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/HubSpot-Academy-Support/bd-p/certifications_help" class="nav-dropdown-link"> Academy Support </a> </li> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/Study-Groups/ct-p/study-groups" class="nav-dropdown-link"> Study Groups </a> </li> </ul> </li> <li class="menu-item has-collapsible"> <a><span></span>Resources</a> <ul class="menu-child jp-resources-list"> <li class="menu-child-item "> <a href="/t5/Getting-Started/ct-p/getting_started" class="nav-dropdown-link nav-discussions-gs"> Getting Started </a> </li> <li class="nav-dropdown-item"> <a href="https://help.hubspot.com/" class="nav-dropdown-link nav-external-link" target="_blank"> Help Center </a> </li> <li class="nav-dropdown-item"> <a href="https://knowledge.hubspot.com/" class="nav-dropdown-link nav-external-link" target="_blank"> Knowledge Base </a> </li> <li class="nav-dropdown-item"> <a href="https://developers.hubspot.com/docs/api/overview" class="nav-dropdown-link nav-external-link" target="_blank"> API Documentation </a> </li> <li class="nav-dropdown-item"> <a href="https://developers.hubspot.com/docs/cms" class="nav-dropdown-link nav-external-link" target="_blank"> CMS Documentation </a> </li> <li class="menu-child-item "> <a href="/t5/News-Networking-Events/ct-p/communityboard" class="nav-dropdown-link nav-news"> News </a> </li> <li class="menu-child-item "> <a href="/t5/Resources/ct-p/resources?node_id=webinars&order_by=last_updated" class="nav-dropdown-link nav-resource-blog-Webinars "> Webinars </a> </li> <li class="menu-child-item "> <a href="/t5/Resources/ct-p/resources?node_id=releases-updates" class="nav-dropdown-link nav-resource-blog-Releases and Updates "> Releases and Updates </a> </li> <li class="menu-child-item "> <a href="/t5/Resources/ct-p/resources?node_id=hubspot-community-blog" class="nav-dropdown-link nav-resource-blog-HubSpot Community Blog "> Community Blog </a> </li> <li class="menu-child-item "> <a href="/t5/Resources/ct-p/resources?node_id=sales-hub-community-perspectives" class="nav-dropdown-link nav-resource-blog-Sales Hub Community Perspectives "> Sales Hub Community Perspectives </a> </li> <li class="menu-child-item "> <a href="/t5/Resources/ct-p/resources?node_id=workflows_library" class="nav-dropdown-link nav-resource-blog-Workflows Library "> Workflows Library </a> </li> <li class="menu-child-item "> <a href="/t5/Resources/ct-p/resources?node_id=ai-library" class="nav-dropdown-link nav-resource-blog-Breeze Library "> Breeze Library </a> </li> </ul> </li> <li class="menu-item has-collapsible"> <a href="#"><span></span>Events</a> <ul class="menu-child"> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/Ask-Me-Anything-and-Panel/bd-p/ama_discussions" class="nav-dropdown-link "> AMA </a> </li> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/Community-Led-Events/bd-p/adapt" class="nav-dropdown-link "> Community Led Events </a> </li> <li class="nav-dropdown-item"> <a href="https://www.hubspot.com/resources/webinar" class="nav-dropdown-link nav-external-link " target="_blank"> Webinars </a> </li> <li class="nav-dropdown-item"> <a href="https://www.hubspot.com/hubspot-user-groups" class="nav-dropdown-link nav-external-link " target="_blank"> HUGS </a> </li> </ul> </li> <li class="menu-item has-collapsible"> <a><span></span>Advocacy</a> <ul class="menu-child"> <li class="menu-child-item"> <a href='/t5/Advocacy/ct-p/advocacy' class="nav-dropdown-link nav-hubFans-program "> Community Champions Program </a> </li> <li class="menu-child-item"> <a href="/t5/Advocates-Blog/bg-p/advocates-blog" class="nav-dropdown-link nav-adovcates-blog "> Champions Blog </a> </li> </ul> </li> </ul> <div class="lang-picker-wrapper"> <div class="lang-picker-container"> <a id="current-language" class="current-language"> <span class="lang-picker-globe-icon"></span> English </a> <div id="lang-picker-global" class="nav-popover lang-picker"> <div class="nav-popover-arrow" style="border-top-color: transparent; border-left-color: transparent; width: 20px; height: 20px; transform: rotate(-135deg); top: -10px; right: calc(50% - 48px);"></div> <ul id="lang-picker-dropdown" class="nav-dropdown-list"> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=en" class="nav-dropdown-link" data-lang="en"> <li class="nav-dropdown-item"> English </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=es" class="nav-dropdown-link" data-lang="es"> <li class="nav-dropdown-item"> Español </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=pt-br" class="nav-dropdown-link" data-lang="pt-br"> <li class="nav-dropdown-item"> Português </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=fr" class="nav-dropdown-link" data-lang="fr"> <li class="nav-dropdown-item"> Français </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=de" class="nav-dropdown-link" data-lang="de"> <li class="nav-dropdown-item"> Deutsch </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=ja" class="nav-dropdown-link" data-lang="ja"> <li class="nav-dropdown-item"> 日本語 </li> </a> </ul> </div> </div></div> </div> <div class="header-right-col"> <div class="search_icon" onclick="myFunction()"> <img src="/html/assets/search-icon-grey.svg?version=preview" alt=""> </div> <div class="wrapper-search" style="display:none"> <div id='lia-searchformV32_2349c9763bdeac' class='SearchForm lia-search-form-wrapper lia-mode-default lia-component-common-widget-search-form'> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='ajaxfeedback_2349c9763bdeac'></div> </div> <div id='searchautocompletetoggle_2349c9763bdeac'> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='ajaxfeedback_2349c9763bdeac_0'></div> </div> <form enctype='multipart/form-data' class='lia-form lia-form-inline SearchForm' action='https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.form.form' method='post' id='form_2349c9763bdeac' name='form_2349c9763bdeac'><div class='t-invisible'><input value='blog-id/community-champion-opportunities/article-id/55' name='t:ac' type='hidden'></input><input value='search/contributions/page' name='t:cp' type='hidden'></input><input value='iS5_lkhS3Bp7qAgryUYuyDquliUZxvD6g9rR4SaPu6HuwzNoES-sRqxVbKu1vT3PSKMpPdasc0wSKbGWFgtKFUfi27zToQP4mzE-KNRs70e3X7zTNxUUON_z7h1KEx_Wa1oAliwk61gXzs9rHliHl195ufgY640bJYTOp0xqg9tTH0UF_HbPFekJW-q9xYKZ82H9l7GjzRHG1LCXyyYALyFa41gn_ulZomMOrB75kZH5yLmtSeZsilLDicVd4c0h4aOCAY7hcYnmHcj2WFTHZyFSEjoPPcqxFoKXU7wE_G_j3dFBz61trbLqXv8dZQridqf6efpp2KJyH1dDqey-m6cGVR0EMNE5tkshKhsuJ7F-YW6KZruIYpLKdjirK6c-1kp5bLyLNkiw-EtZwoYXdjeXpyKNIVuGY0nn9jhNBFualwOFWYFjCAuS8HO8h_MGTB6_km6_6R0JRH9F-KIHCoHDEGTeK9Exa9tiq7zGkGRJAWkaFF5CcoPAaCCfSFd0WDoryZ8XTwdn1Q3NuTJl6__OF3Lu8j-I6m7EwM0Si7HHG_NxV28Cl3pIS4Qe7ZK0rjUUlw69NiBV_EJMRRTX9Sssk5eClsvnnlLrywJAgWU_FxV0CXc4awqS1gU6ISG2l48wogNWJvAomcWSLhrKo_oK1RvL6GuIiRhitDlnwA0yGYtNDeeO9shJ8_3cve0A4Rv8R747-J9RMW8YYd7C1Ww8cf7snBAvfIuOTLDXFLRUSGKYIdQRBNoBImG7Wo5g8pDj3HOqoO20sdAavrS_KcF7czX_lfVL_G_Ne_zcat3lLSJdYtGshh74KAZb6QDJOGZBEBpKSBWP1CvIcJs3bIE0hxEKBfpIzIc5EBSAzyFvsUshubUy_X-QHq-uHtNaoKwysfNOMSfQ_55D38NZoAf8SMuUVvvmg3yy1bxY9wuI0ooI0UCsZ7dxVR9VyZ96p8wg6scCwJFfT6EazhVlMMZO1upDS0xugKnPO2XtFWkCV3YYuLpHq1d_KdZhG-3yUa-C0-j_FnP3TP4ptvJqrVBwiRGhNKa90Fd4JxOU90SFEoGwlLiTPEvrUNgndbAFysCWIR7J3NUDJrd9NZ1kA0L9tu-fqBN2Tcf5c_Qi0hYTLS7LnJaTPS_LCOpAa-_HbXU3U-tAPkrGanglXGkXOq_bSETbqtqpU_aSKhfbgZc.' name='lia-form-context' type='hidden'></input><input value='BlogArticlePage:blog-id/community-champion-opportunities/article-id/55:searchformv32.form:' name='liaFormContentKey' type='hidden'></input><input value='yDZKQFNI0U/sd9X4pbUemqvQQZw=:H4sIAAAAAAAAALWSzUrDQBSFr4Wuigiib6DbiajdqAhFUISqweBaZibTNJpk4sxNEzc+ik8gvkQX7nwHH8CtKxfmzxJbwaTUVZhzw/nOmblP79COD+FAC6r40OAyQOWyCF0ZaMOkjtgrJgOp/NHONtER810sP9nfIkGtoCuVQ2hI+VAQpKHQqO67hEslPJcRRrUgPZaKlOOxKzx7wxIYhZtX487b+stnC5b60MnZ0junvkBY7d/QETU8GjiGlUYKnP0kRFguwEcFeAHBe02Dm0pyobWV+Wid0sbP9u7g4/G1BZCE8QWc1U3kpzapWoqZ+S+SvoMHgPQ+ypGVj/IoC2dlqHZ8CWZdV7xljUqszZa43voPYNHkFE7qGkdaqKrl1Pm7wEqmV59gcYjGkQOJP25h6jyJnOlzRv4DUURusIWhknbEsWo5K002vhzNufG1WHmDLwdzh8gDBQAA' name='t:formdata' type='hidden'></input></div> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='feedback_2349c9763bdeac'></div> </div> <input value='bY9-__aIAfc1cW7Cu67QbhKC-1f2m5MkjsiglmBc690.' name='lia-action-token' type='hidden'></input> <input value='form_2349c9763bdeac' id='form_UIDform_2349c9763bdeac' name='form_UID' type='hidden'></input> <input value='' id='form_instance_keyform_2349c9763bdeac' name='form_instance_key' type='hidden'></input> <span class='lia-search-input-wrapper'> <span class='lia-search-input-field'> <span class='lia-button-wrapper lia-button-wrapper-secondary lia-button-wrapper-searchForm-action'><input value='searchForm' name='submitContextX' type='hidden'></input><input class='lia-button lia-button-secondary lia-button-searchForm-action' value='Search' id='submitContext_2349c9763bdeac' name='submitContext' type='submit'></input></span> <input placeholder='Search the Community' aria-label='Search' title='Search' class='lia-form-type-text lia-autocomplete-input search-input lia-search-input-message' value='' id='messageSearchField_2349c9763bdeac_0' name='messageSearchField' type='text'></input> <input placeholder='Search the Community' aria-label='Search' title='Search' class='lia-form-type-text lia-autocomplete-input search-input lia-search-input-tkb-article lia-js-hidden' value='' id='messageSearchField_2349c9763bdeac_1' name='messageSearchField_0' type='text'></input> <input ng-non-bindable='' title='Enter a user name or rank' class='lia-form-type-text UserSearchField lia-search-input-user search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a user name or rank' value='' id='userSearchField_2349c9763bdeac' name='userSearchField' type='text'></input> <input placeholder='Enter a keyword to search within the private messages' title='Enter a search word' class='lia-form-type-text NoteSearchField lia-search-input-note search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a search word' value='' id='noteSearchField_2349c9763bdeac_0' name='noteSearchField' type='text'></input> <input title='Enter a search word' class='lia-form-type-text ProductSearchField lia-search-input-product search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a search word' value='' id='productSearchField_2349c9763bdeac' name='productSearchField' type='text'></input> <input class='lia-as-search-action-id' name='as-search-action-id' type='hidden'></input> </span> </span> <span class='lia-cancel-search'>cancel</span> </form> <div class='search-autocomplete-toggle-link lia-js-hidden'> <span> <a class='lia-link-navigation auto-complete-toggle-on lia-link-ticket-post-action lia-component-search-action-enable-auto-complete' data-lia-action-token='-4UnW9n5O_4vBg1bhfd5D1WD5jI8wgntKAo2id_W9ZM.' rel='nofollow' id='enableAutoComplete_2349c9763bdeac' href='https://community.hubspot.com/t5/blogs/v2/blogarticlepage.enableautocomplete:enableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions'>Turn on suggestions</a> <span class='HelpIcon'> <a class='lia-link-navigation help-icon lia-tooltip-trigger' role='button' aria-label='Help Icon' id='link_2349c9763bdeac' href='#'><span class='lia-img-icon-help lia-fa-icon lia-fa-help lia-fa' alt='Auto-suggest helps you quickly narrow down your search results by suggesting possible matches as you type.' aria-label='Help Icon' role='img' id='display_2349c9763bdeac'></span></a><div role='alertdialog' class='lia-content lia-tooltip-pos-bottom-left lia-panel-tooltip-wrapper' id='link_2349c9763bdeac_0-tooltip-element'><div class='lia-tooltip-arrow'></div><div class='lia-panel-tooltip'><div class='content'>Auto-suggest helps you quickly narrow down your search results by suggesting possible matches as you type.</div></div></div> </span> </span> </div> </div> <div class='spell-check-showing-result'> Showing results for <span class='lia-link-navigation show-results-for-link lia-link-disabled' aria-disabled='true' id='showingResult_2349c9763bdeac'></span> </div> <div> <span class='spell-check-search-instead'> Search instead for <a class='lia-link-navigation search-instead-for-link' rel='nofollow' id='searchInstead_2349c9763bdeac' href='#'></a> </span> </div> <div class='spell-check-do-you-mean lia-component-search-widget-spellcheck'> Did you mean: <a class='lia-link-navigation do-you-mean-link' rel='nofollow' id='doYouMean_2349c9763bdeac' href='#'></a> </div> </div> </div> <script> function myFunction() { var x = document.getElementsByClassName("wrapper-search")[0]; if (x.style.display === "none") { x.style.display = "block"; } else { x.style.display = "none"; } } </script><div class="search-icon-plus-top"> <button class="lia-button search-toggle-action-icon-plus"><img src='https://community.hubspot.com/html/@5320E40129AA1377479EABCA2009B53A/assets/Start-dicsucssion.svg' alt=""><i class="lia-fa lia-fa-caret-down"></i></button> <div class="plus-bar-main-content" style="display: none;"> <ul id="plus-bar-top-main"> <li class="plus-bar"> <a href="https://app.hubspot.com/khoros/integration/jwt/authenticate?referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2Fforums%2Fpostpage%2Fcategory-id%2Fhubspot_community_en%2Fchoose-node%2Ftrue" class="white-btn transpaent"><i><img src="https://community.hubspot.com/html/@38F5B2AB35958F39F47C2BFFE5486135/assets/Edit.svg"></i> Create post</a> </li> <li class="plus-bar"><a href="https://app.hubspot.com/khoros/integration/jwt/authenticate?referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2Fforums%2Fpostpage%2Fboard-id%2FHubSpot_Ideas"><i><img src="https://community.hubspot.com/html/@C71A42AEB76B8A3A82335DA9F5B9C717/assets/lightbulb.svg"></i> Submit Idea</a></li> </ul> </div> </div> <div class="login-container"> <a class='lia-link-navigation login-link lia-authentication-link lia-component-users-action-login' rel='nofollow' id='loginPageV2_2349c976d788fb' href='https://app.hubspot.com/khoros/integration/jwt/authenticate?referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2FCommunity-Champion-Opportunities%2FShare-your-review-of-Adobe-Express%2Fba-p%2F1062774'>Log in</a> </div> </div> </nav> </div> <script> const openMenu = document.querySelector(".open-menu"); const closeMenu = document.querySelector(".close-menu"); const menuWrapper = document.querySelector(".menu-wrapper"); const hasCollapsible = document.querySelectorAll(".has-collapsible"); // Sidenav Toggle openMenu.addEventListener("click", function () { menuWrapper.classList.add("offcanvas"); }); closeMenu.addEventListener("click", function () { menuWrapper.classList.remove("offcanvas"); }); // Collapsible Menu hasCollapsible.forEach(function (collapsible) { collapsible.addEventListener("click", function () { collapsible.classList.toggle("active"); // Close Other Collapsible hasCollapsible.forEach(function (otherCollapsible) { if (otherCollapsible !== collapsible) { otherCollapsible.classList.remove("active"); } }); }); }); </script> </div> </div> </div><div class="lia-quilt-row lia-quilt-row-main"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-main-content"> <div class="lia-quilt-column-alley lia-quilt-column-alley-single"> <script> // Inline click listeners function getHubSpotClickListener(btnId) { if(btnId == "get-hubspot-free-v2"){ if (document.getElementById('get-hubspot-v2')) { document.getElementById('get-hubspot-v2').classList.toggle("show"); } } } function langPickerClickListener() { if (document.getElementById('lang-picker-global')) { document.getElementById('lang-picker-global').classList.toggle("show"); } } </script> <style> /* Text customization for CSS generated content */ .lia-list-row-thread-solved:after { content: "Solved"!important; text-transform: uppercase; } .SearchPage .lia-replies-toggle-link:before { content: "Replies"!important; } label.lia-form-label:after { // content: "(required)"; margin-left: 4px; } .lia-form-board-entry label.lia-form-label:after, .lia-form-subject-entry label.lia-form-label:after, .lia-form-body-entry label.lia-form-label:after, .lia-form-labels-entry label.lia-form-label:after { content: "(required)"; } .lia-form-login-entry .lia-form-input-wrapper:before { content: "Username*"!important; } .lia-form-profile-first-name-entry:before { content: "First name*"!important; } .lia-form-profile-last-name-entry:before { content: "Last name*"!important; } .lia-note-unread:after { content: "Unread"; text-transform: uppercase; } .EditPage label.lia-form-label:after, .EditPage .lia-form-label.lia-fieldset-title:after, .EditPage .lia-component-tkb-article-editor-form .lia-form-label.lia-form-compare-title:after, .lia-component-tkb-article-editor-form .EditPage .lia-form-label.lia-form-compare-title:after, .EditPage .lia-component-tkb-article-editor-form .lia-form-label.lia-revision-info-title:after, .lia-component-tkb-article-editor-form .EditPage .lia-form-label.lia-revision-info-title:after, .EditPage .lia-component-tkb-article-editor-form .lia-form-label.lia-related-messages-title:after, .lia-component-tkb-article-editor-form .EditPage .lia-form-label.lia-related-messages-title:after, .ReplyPage label.lia-form-label:after, .ReplyPage .lia-form-label.lia-fieldset-title:after, .ReplyPage .lia-component-tkb-article-editor-form .lia-form-label.lia-form-compare-title:after, .lia-component-tkb-article-editor-form .ReplyPage .lia-form-label.lia-form-compare-title:after, .ReplyPage .lia-component-tkb-article-editor-form .lia-form-label.lia-revision-info-title:after, .lia-component-tkb-article-editor-form .ReplyPage .lia-form-label.lia-revision-info-title:after, .ReplyPage .lia-component-tkb-article-editor-form .lia-form-label.lia-related-messages-title:after, .lia-component-tkb-article-editor-form .ReplyPage .lia-form-label.lia-related-messages-title:after, .PostPage label.lia-form-label:after, .PostPage .lia-form-label.lia-fieldset-title:after, .PostPage .lia-component-tkb-article-editor-form .lia-form-label.lia-form-compare-title:after, .lia-component-tkb-article-editor-form .PostPage .lia-form-label.lia-form-compare-title:after, .PostPage .lia-component-tkb-article-editor-form .lia-form-label.lia-revision-info-title:after, .lia-component-tkb-article-editor-form .PostPage .lia-form-label.lia-revision-info-title:after, .PostPage .lia-component-tkb-article-editor-form .lia-form-label.lia-related-messages-title:after, .lia-component-tkb-article-editor-form .PostPage .lia-form-label.lia-related-messages-title:after, .MyProfilePage label.lia-form-label:after, .MyProfilePage .lia-form-label.lia-fieldset-title:after, .MyProfilePage .lia-component-tkb-article-editor-form .lia-form-label.lia-form-compare-title:after, .lia-component-tkb-article-editor-form .MyProfilePage .lia-form-label.lia-form-compare-title:after, .MyProfilePage .lia-component-tkb-article-editor-form .lia-form-label.lia-revision-info-title:after, .lia-component-tkb-article-editor-form .MyProfilePage .lia-form-label.lia-revision-info-title:after, .MyProfilePage .lia-component-tkb-article-editor-form .lia-form-label.lia-related-messages-title:after, .lia-component-tkb-article-editor-form .MyProfilePage .lia-form-label.lia-related-messages-title:after, .KudosMessagePage label.lia-form-label:after, .KudosMessagePage .lia-form-label.lia-fieldset-title:after, .KudosMessagePage .lia-component-tkb-article-editor-form .lia-form-label.lia-form-compare-title:after, .lia-component-tkb-article-editor-form .KudosMessagePage .lia-form-label.lia-form-compare-title:after, .KudosMessagePage .lia-component-tkb-article-editor-form .lia-form-label.lia-revision-info-title:after, .lia-component-tkb-article-editor-form .KudosMessagePage .lia-form-label.lia-revision-info-title:after, .KudosMessagePage .lia-component-tkb-article-editor-form .lia-form-label.lia-related-messages-title:after, .lia-component-tkb-article-editor-form .KudosMessagePage .lia-form-label.lia-related-messages-title:after { // content: "(required)"; } .ForumPage span.in-english:after, .CategoryPage span.in-english:after { content: "EN"; } a.in-english:after { content: "EN"; } .nav-menu a.in-english:after { content: "EN"; } .header-search-wrapper{margin-right:5px;margin-top: 33px;} #lia-body .nav-wrapper .header-search-wrapper .lia-search-form-wrapper{position: relative;left: 0px;border:none;box-shadow:none;max-width: 460px;width: 100%;} #lia-body .nav-wrapper .header-search-wrapper .lia-button-searchForm-action{top:8px !important;position: absolute;right: 6px;min-width: auto !important; background-color: transparent !important;border: none !important;max-height: 18px !important; background-size:contain !important;background-image: url(/html/assets/search-icon.svg)} #lia-body .nav-wrapper .header-search-wrapper .search-input::-webkit-input-placeholder{font-size: 16px !important;} #lia-body .nav-wrapper .header-search-wrapper .search-input{height: 32px !important;padding: 10px !important;padding-right: 50px !important;border: 1px solid #CBD6E2 !important;box-shadow: none !important;background-color: white !important;font-size: 16px !important; border-radius: 3px !important;color: #33475B !important;line-height:24px !important;} #lia-body .header-search-wrapper .lia-search-input-wrapper {width: 460px !important;} #lia-body .nav-wrapper .header-search-wrapper .lia-search-input-wrapper input:focus {box-shadow: 0 0 4px 1px rgb(255 255 255 / 30%), 0 0 0 1px #fff!important;} .header-search-wrapper .lia-search-granularity-wrapper .lia-search-form-granularity{display:none !important;} .header-search-wrapper .lia-search-granularity-wrapper:before{display:none !important;} @media only screen and (max-width: 992px) { #lia-body .nav-wrapper .header-search-wrapper .lia-search-form-wrapper{z-index: 1;} } @media only screen and (max-width: 767px) { .user-nav-bar{ position: relative; height: 100%; } #lia-body .header-search-wrapper .lia-search-input-wrapper{ width: 100% !important; max-width: none;} #lia-body .nav-wrapper .header-search-wrapper .lia-search-form-wrapper{padding: 10px 24px !important; background: #fff;border: 1px solid #eee2e2;margin-left: 4px;max-width:none !important;width: 100%;} #lia-body .nav-wrapper .header-search-wrapper .lia-button-searchForm-action {right:20px;top:17px !important;} .header-search-wrapper{display:none; position: absolute; top: 121px; width: 100% !important;} #lia-body .nav-wrapper .header-search-wrapper .lia-search-input-message{ margin-right:-7px;} #lia-body .nav-wrapper .header-search-wrapper .lia-search-form-wrapper::before{position: absolute; top: -6px;content: "";right: 67px;width: 10px;height: 10px;background-color: white;transform: rotate(45deg);border-top: 1px solid #eee2e2; border-left: 1px solid #eee2e2;} } </style> <section class="community-header-nav v2"> <div class="nav-wrapper"> <div class="user-nav-bar"> <nav class="nav-menu template-centered template-section"> <div class="lang-picker-wrapper"> <div class="lang-picker-container"> <a id="current-language" class="current-language"> <span class="lang-picker-globe-icon"></span> English </a> <div id="lang-picker-global" class="nav-popover lang-picker"> <div class="nav-popover-arrow" style="border-top-color: transparent; border-left-color: transparent; width: 20px; height: 20px; transform: rotate(-135deg); top: -10px; right: calc(50% - 48px);"></div> <ul id="lang-picker-dropdown" class="nav-dropdown-list"> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=en" class="nav-dropdown-link" data-lang="en"> <li class="nav-dropdown-item"> English </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=es" class="nav-dropdown-link" data-lang="es"> <li class="nav-dropdown-item"> Español </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=pt-br" class="nav-dropdown-link" data-lang="pt-br"> <li class="nav-dropdown-item"> Português </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=fr" class="nav-dropdown-link" data-lang="fr"> <li class="nav-dropdown-item"> Français </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=de" class="nav-dropdown-link" data-lang="de"> <li class="nav-dropdown-item"> Deutsch </li> </a> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774?profile.language=ja" class="nav-dropdown-link" data-lang="ja"> <li class="nav-dropdown-item"> 日本語 </li> </a> </ul> </div> </div></div> <div class="header-search-wrapper"> <div id='lia-searchformV32_2349c976f108e4' class='SearchForm lia-search-form-wrapper lia-mode-default lia-component-common-widget-search-form'> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='ajaxfeedback_2349c976f108e4'></div> </div> <div id='searchautocompletetoggle_2349c976f108e4'> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='ajaxfeedback_2349c976f108e4_0'></div> </div> <form enctype='multipart/form-data' class='lia-form lia-form-inline SearchForm' action='https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.form.form' method='post' id='form_2349c976f108e4' name='form_2349c976f108e4'><div class='t-invisible'><input value='blog-id/community-champion-opportunities/article-id/55' name='t:ac' type='hidden'></input><input value='search/contributions/page' name='t:cp' type='hidden'></input><input value='dIUGAmG3q77HOEUgflUI3SLxRq74p7zaOUgPaOijtk6rXj1M1K5QGzq-AC4sjuKy9AB781akyi239O-IMyaXCmbeNI7xbiUmywDToiuKNgtEh2rqg5LOtdv-idoKGP3i96hMO-YzwAdIvDK5zyghY1hVkpVdeHHZ3h26gmlXIqpVNthvE61dwmYhSFc919we0ZFgPEbmQhsP-T2m6GUjtm5i49LlwJ9ivybcZfl4qjexE8qFJtbpXSuloyr_w5EtNdM8Z5sYH0yfnHWVsmscolYE0xv1pMi4oc0sOssSD4hOeU2aN2HB_C0BxMZx-T8TeJEgv9tJ3QnqbxUc9-t9ezW023bYHfBVQdiINauWobVMC4zauqaZSKc18LUjO5PIms8Qf0JaL8KSguAR3RjD01qPGMDUBX5lY7u-hNOs0h8XHvWd-ljyfihjx5PE71oF19tpzYoWe2tweZOmfmhoBbBpXTCF7KuLWqFN5hGjLbqTbJrqqdPt76oMDHCagX48puOH1QHqbrjCiLxCW4JgcEnOaKvqHyWdJWdXhT9jh1k9ipOd12bK1rOlwBdDDfYZAiRbiwu7DvTMF_VJknHbNSfxko6cO1CiFXBzToZkaT-597yZLotJYdiWS484IReetfCsynUkmWhAbTNetbEmzebCMw_Uw9aPxIDg_WKNCeZYprL-fDl8_aRe9EeWLGSK5pCj2eRlrU0mgCwA2ve9_Z4tk9dg0JtO8NCF5EkDTack0QRAzmTXFuP-e7zFGVHGNoHAnaD4bRAsAHbickf--xAwjI32km3gcB7EJh_t_T-TW4oX0Lh_1gCO0m-kB1G61gzgdVy5DBYNpAbjxmWDWDr0DBL2yaaTUznhcR45Q9x41_2vAFcezjs02NgcuiNIta4hfr5SK9IiGHmz_K7Sr92y6GfXA8cU8LUveL60UpDegJ1OU_hI2pX9CMoK-Xa8-wFFmrNpX1bQDXVHssb76USIMTgTkaNJv9_NubY9E_iDO1PSCTk8Zq8O-ImrbEtDRwcmuk1vPi3o3ZurfQmRjt0n9q5x42pOg91KEF1I6Q_UuWF4WiGE45ww6ajNVXl0iO8UWl_dSzFZNoY_6sr_kN33AghDW3kJiC-YKgcjStpBz5fQhrln6VKbrNrhMdR7--RamoiPnpDUVuwUaNqGrzl9XGfGGJQvBVi_IJBp4drxwIciryPsmJocgR2mn9bNNMM7aXscAUmp-HaT3-tdMj9p56p-k3pd8ccr7IG7akk.' name='lia-form-context' type='hidden'></input><input value='BlogArticlePage:blog-id/community-champion-opportunities/article-id/55:searchformv32.form:' name='liaFormContentKey' type='hidden'></input><input value='5DI9GWMef1Esyz275vuiiOExwpQ=:H4sIAAAAAAAAALVSTU7CQBR+krAixkj0BrptjcpCMSbERGKCSmxcm+kwlGrbqTOvFDYexRMYL8HCnXfwAG5dubDtFKxgYgu4mrzvm3w/M+/pHcphHQ4kI4L2dMo9FLYZoM09qbeJxQ4V0+XC7e/tamqyBPEChwgbh1JAjQtLIz6hPaYh8ZlEMaxplAvm2KZmEsm0hhmBhOKpzZzOlsEw8LevR5W3zZfPEqy0oJIYc+eCuAyh2rolfaI7xLN0I8rjWfWBj7CuzJvf5osmbxRN3hacMimNwHRtKSOr0XNnv/vx+FoCGPjhMRzljhNLYHrEt9kA5T08ACCsKvREoYuqxqLl8BLO84q4UcMITcG49y/QOGs1pYyESl5p6V6qwRW086rinVmoxMZsiZud/zBUTc6gmVc4kExkJafmcYG1GM9+wfIsCkf2OP54hal5EjnG54z8h0XhjfcF7wQUs5Kz0GTjU2rOjc/llTT4Au07pDOcBQAA' name='t:formdata' type='hidden'></input></div> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='feedback_2349c976f108e4'></div> </div> <input value='ZLPyKxYQy1qp4W80HQUV1pDAjwxQDxJVa3_FEcpXfz0.' name='lia-action-token' type='hidden'></input> <input value='form_2349c976f108e4' id='form_UIDform_2349c976f108e4' name='form_UID' type='hidden'></input> <input value='' id='form_instance_keyform_2349c976f108e4' name='form_instance_key' type='hidden'></input> <span class='lia-search-granularity-wrapper'> <select title='Search Granularity' class='lia-search-form-granularity search-granularity' aria-label='Search Granularity' id='searchGranularity_2349c976f108e4' name='searchGranularity'><option title='All Results' selected='selected' value='mjmao93648|community'>All Results</option><option title='This forum' value='advocacy|category'>This forum</option><option title='Blog' value='community-champion-opportunities|blog-board'>Blog</option><option title='Users' value='user|user'>Users</option></select> </span> <span class='lia-search-input-wrapper'> <span class='lia-search-input-field'> <span class='lia-button-wrapper lia-button-wrapper-secondary lia-button-wrapper-searchForm-action'><input value='searchForm' name='submitContextX' type='hidden'></input><input class='lia-button lia-button-secondary lia-button-searchForm-action' value='Search' id='submitContext_2349c976f108e4' name='submitContext' type='submit'></input></span> <input placeholder='Search the Community' aria-label='Search' title='Search' class='lia-form-type-text lia-autocomplete-input search-input lia-search-input-message' value='' id='messageSearchField_2349c976f108e4_0' name='messageSearchField' type='text'></input> <input placeholder='Search the Community' aria-label='Search' title='Search' class='lia-form-type-text lia-autocomplete-input search-input lia-search-input-tkb-article lia-js-hidden' value='' id='messageSearchField_2349c976f108e4_1' name='messageSearchField_0' type='text'></input> <input ng-non-bindable='' title='Enter a user name or rank' class='lia-form-type-text UserSearchField lia-search-input-user search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a user name or rank' value='' id='userSearchField_2349c976f108e4' name='userSearchField' type='text'></input> <input placeholder='Enter a keyword to search within the private messages' title='Enter a search word' class='lia-form-type-text NoteSearchField lia-search-input-note search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a search word' value='' id='noteSearchField_2349c976f108e4_0' name='noteSearchField' type='text'></input> <input title='Enter a search word' class='lia-form-type-text ProductSearchField lia-search-input-product search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a search word' value='' id='productSearchField_2349c976f108e4' name='productSearchField' type='text'></input> <input class='lia-as-search-action-id' name='as-search-action-id' type='hidden'></input> </span> </span> <span class='lia-cancel-search'>cancel</span> </form> <div class='search-autocomplete-toggle-link lia-js-hidden'> <span> <a class='lia-link-navigation auto-complete-toggle-on lia-link-ticket-post-action lia-component-search-action-enable-auto-complete' data-lia-action-token='1CC8vT0W9y22SNnTBT4NNSjoSnWk22nU3hcuJ6R1yPI.' rel='nofollow' id='enableAutoComplete_2349c976f108e4' href='https://community.hubspot.com/t5/blogs/v2/blogarticlepage.enableautocomplete:enableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions'>Turn on suggestions</a> <span class='HelpIcon'> <a class='lia-link-navigation help-icon lia-tooltip-trigger' role='button' aria-label='Help Icon' id='link_2349c976f108e4' href='#'><span class='lia-img-icon-help lia-fa-icon lia-fa-help lia-fa' alt='Auto-suggest helps you quickly narrow down your search results by suggesting possible matches as you type.' aria-label='Help Icon' role='img' id='display_2349c976f108e4'></span></a><div role='alertdialog' class='lia-content lia-tooltip-pos-bottom-left lia-panel-tooltip-wrapper' id='link_2349c976f108e4_0-tooltip-element'><div class='lia-tooltip-arrow'></div><div class='lia-panel-tooltip'><div class='content'>Auto-suggest helps you quickly narrow down your search results by suggesting possible matches as you type.</div></div></div> </span> </span> </div> </div> <div class='spell-check-showing-result'> Showing results for <span class='lia-link-navigation show-results-for-link lia-link-disabled' aria-disabled='true' id='showingResult_2349c976f108e4'></span> </div> <div> <span class='spell-check-search-instead'> Search instead for <a class='lia-link-navigation search-instead-for-link' rel='nofollow' id='searchInstead_2349c976f108e4' href='#'></a> </span> </div> <div class='spell-check-do-you-mean lia-component-search-widget-spellcheck'> Did you mean: <a class='lia-link-navigation do-you-mean-link' rel='nofollow' id='doYouMean_2349c976f108e4' href='#'></a> </div> </div> </div> <div class="user-nav-options"> <!-- community-header-nav-upper-v2 --> <style>.get-hubspot .nav-popover-arrow {border-top-color: transparent; border-left-color: transparent; width: 20px; height: 20px; transform: rotate(-135deg); top: -10px; display: block;}</style> <div class="search-icon-plus-top"> <button class="lia-button search-toggle-action-icon-plus"><img src='https://community.hubspot.com/html/@5320E40129AA1377479EABCA2009B53A/assets/Start-dicsucssion.svg' alt=""><i class="lia-fa lia-fa-caret-down"></i></button> <div class="plus-bar-main-content" style="display: none;"> <ul id="plus-bar-top-main"> <li class="plus-bar"> <a href="https://app.hubspot.com/khoros/integration/jwt/authenticate?referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2Fforums%2Fpostpage%2Fcategory-id%2Fhubspot_community_en%2Fchoose-node%2Ftrue" class="white-btn transpaent"><i><img src="https://community.hubspot.com/html/@38F5B2AB35958F39F47C2BFFE5486135/assets/Edit.svg"></i> Create post</a> </li> <li class="plus-bar"><a href="https://app.hubspot.com/khoros/integration/jwt/authenticate?referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2Fforums%2Fpostpage%2Fboard-id%2FHubSpot_Ideas"><i><img src="https://community.hubspot.com/html/@C71A42AEB76B8A3A82335DA9F5B9C717/assets/lightbulb.svg"></i> Submit Idea</a></li> </ul> </div> </div> <div class="login-container"> <a class='lia-link-navigation login-link lia-authentication-link lia-component-users-action-login' rel='nofollow' id='loginPageV2_2349c9776b01c9' href='https://app.hubspot.com/khoros/integration/jwt/authenticate?referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2FCommunity-Champion-Opportunities%2FShare-your-review-of-Adobe-Express%2Fba-p%2F1062774'>Log in</a> <a id="get-hubspot-free" class="btn btn-sm button-tertiary get-hubspot">Get HubSpot free</a> <div id="get-hubspot" class="get-hubspot"> <div class="nav-popover-arrow"></div> <ul id="get-hs-dropdown" class="nav-dropdown-list"> <li class="nav-dropdown-item nav-crm"> <a href="https://www.hubspot.com/products/get-started" class="nav-dropdown-link nav-crm"> Get a HubSpot CRM account </a> </li> <li class="nav-dropdown-item nav-dev"> <a href="https://developers.hubspot.com/" class="nav-dropdown-link nav-dev"> Get a HubSpot developer account </a> </li> </ul> </div> </div> </div> </nav> </div> <div class="forum-nav-bar"> <nav class="nav-menu template-centered template-section"> <a href="/" id="nav-logo" class="en nav-wordmark"> <img src='https://community.hubspot.com/html/@60C06466C7735C4373198758B1428669/assets/Community-logo-new.svg' alt="hubspot" class="nav-wordmark"> <span class="page-title nav-wordmark">Community</span> </a> <a href="/" id="nav-logo-v2" class="en nav-wordmark v2"> <img src='https://community.hubspot.com/html/@813D252A70F0A7024C8EA3BB1B8B9CFD/assets/sticky-logo.png' alt="hubspot" class="nav-wordmark"> </a> <style> .jp-resources-list{ display:flex; flex-direction:column; } .jp-resources-list>li.nav-dropdown-item.jp-class-HubSpot.Community.Blog{ order:1; } .nav-external-link::after { background: none; content: "\f08e"; font-family: "FontAwesome"; margin-left: 5px; font-size: 14px; } </style> <div class="nav-group-wrapper"> <div class="nav-menu"> <ul class="nav-group-primary"> <li class="nav-group-item nav-group-item-has-dropdown"> <div class="nav-link-wrapper"> <a class="nav-link forum"> <span class="nav-link-label"> Discussions </span> <span class="dropdown-caret"></span> </a> <div class="nav-popover-arrow"></div> <div class="nav-popover forum"> <ul class="nav-dropdown-list"> <li class="nav-dropdown-item"> <a href="/t5/CRM-Sales-Hub/ct-p/sales" class="nav-dropdown-link nav-discussions-crm"> CRM & Sales </a> </li> <li class="nav-dropdown-item"> <a href="/t5/Marketing-Hub/ct-p/marketing" class="nav-dropdown-link nav-discussions-mktg"> Marketing & Content </a> </li> <li class="nav-dropdown-item"> <a href="/t5/Service-Hub/ct-p/service_hub" class="nav-dropdown-link nav-discussions-svc"> Customer Success & Service </a> </li> <li class="nav-dropdown-item"> <a href="/t5/Operations/ct-p/Operations" class="nav-dropdown-link nav-discussions-ops"> RevOps & Operations </a> </li> <li class="nav-dropdown-item"> <a href="/t5/Commerce/ct-p/commerce" class="nav-dropdown-link nav-discussions-commerce "> Commerce </a> </li> <li class="nav-dropdown-item"> <a href="/t5/HubSpot-Developers/ct-p/developers" class="nav-dropdown-link nav-discussions-developers "> Developers </a> </li> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/Getting-Started-on-the-Community/How-to-join-the-Solutions-Partner-Program/ba-p/400205" class="nav-dropdown-link nav-partners "> Partners </a> </li> <li class="nav-dropdown-item"> <a href="/t5/HubSpot-Ideas/idb-p/HubSpot_Ideas" class="nav-dropdown-link nav-discussions-ideas"> Ideas </a> </li> </ul> </div> </div> </li> <li class="nav-group-item nav-group-item-has-dropdown secondInList"> <div class="nav-link-wrapper"> <a class="nav-link forum"> <span class="nav-link-label"> Academy </span> <span class="dropdown-caret"></span> </a> <div class="nav-popover-arrow"></div> <div class="nav-popover forum"> <ul class="nav-dropdown-list"> <li class="nav-dropdown-item"> <a href="https://academy.hubspot.com/courses" class="nav-dropdown-link nav-external-link" target="_blank"> Courses </a> </li> <li class="nav-dropdown-item"> <a href="https://academy.hubspot.com/certification-overview" class="nav-dropdown-link nav-external-link" target="_blank"> Certifications </a> </li> <li class="nav-dropdown-item"> <a href="https://www.hubspot.com/academy/bootcamps/home" class="nav-dropdown-link nav-external-link" target="_blank"> Bootcamps </a> </li> <li class="nav-dropdown-item"> <a href="https://academy.hubspot.com/learning-paths" class="nav-dropdown-link nav-external-link" target="_blank"> Learning Paths </a> </li> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/HubSpot-Academy-Support/bd-p/certifications_help" class="nav-dropdown-link "> Academy Support </a> </li> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/Study-Groups/ct-p/study-groups" class="nav-dropdown-link"> Study Groups </a> </li> </ul> </div> </div> </li> <li class="nav-group-item"> <div class="nav-link-wrapper"> <a class="nav-link nav-resources"> <span class="nav-link-label"> Resources </span> <span class="dropdown-caret"></span> </a> <div class="nav-popover-arrow"></div> <div class="nav-popover forum"> <ul class="nav-dropdown-list jp-resources-list"> <li class="nav-dropdown-item "> <a href="/t5/Getting-Started/ct-p/getting_started" class="nav-dropdown-link nav-discussions-gs"> Getting Started </a> </li> <li class="nav-dropdown-item"> <a href="https://help.hubspot.com/" class="nav-dropdown-link nav-external-link" target="_blank"> Help Center </a> </li> <li class="nav-dropdown-item"> <a href="https://knowledge.hubspot.com/" class="nav-dropdown-link nav-external-link" target="_blank"> Knowledge Base </a> </li> <li class="nav-dropdown-item"> <a href="https://developers.hubspot.com/docs/api/overview" class="nav-dropdown-link nav-external-link" target="_blank"> API Documentation </a> </li> <li class="nav-dropdown-item"> <a href="https://developers.hubspot.com/docs/cms" class="nav-dropdown-link nav-external-link" target="_blank"> CMS Documentation </a> </li> <li class="nav-dropdown-item "> <a href="/t5/News-Networking-Events/ct-p/communityboard" class="nav-dropdown-link nav-news"> News </a> </li> <li class="nav-dropdown-item "> <a href="/t5/Resources/ct-p/resources?node_id=webinars&order_by=last_updated" class="nav-dropdown-link nav-resource-blog-Webinars "> Webinars </a> </li> <li class="nav-dropdown-item "> <a href="/t5/Resources/ct-p/resources?node_id=releases-updates" class="nav-dropdown-link nav-resource-blog-Releases and Updates "> Releases and Updates </a> </li> <li class="nav-dropdown-item "> <a href="/t5/Resources/ct-p/resources?node_id=hubspot-community-blog" class="nav-dropdown-link nav-resource-blog-HubSpot Community Blog "> Community Blog </a> </li> <li class="nav-dropdown-item "> <a href="/t5/Resources/ct-p/resources?node_id=sales-hub-community-perspectives" class="nav-dropdown-link nav-resource-blog-Sales Hub Community Perspectives "> Sales Hub Community Perspectives </a> </li> <li class="nav-dropdown-item "> <a href="/t5/Resources/ct-p/resources?node_id=workflows_library" class="nav-dropdown-link nav-resource-blog-Workflows Library "> Workflows Library </a> </li> <li class="nav-dropdown-item "> <a href="/t5/Resources/ct-p/resources?node_id=ai-library" class="nav-dropdown-link nav-resource-blog-Breeze Library "> Breeze Library </a> </li> </ul> </div> </div> </li> <li class="nav-group-item nav-group-item-has-dropdown"> <div class="nav-link-wrapper"> <a class="nav-link forum"> <span class="nav-link-label"> Events </span> <span class="dropdown-caret"></span> </a> <div class="nav-popover-arrow"></div> <div class="nav-popover forum"> <ul class="nav-dropdown-list"> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/Ask-Me-Anything-and-Panel/bd-p/ama_discussions" class="nav-dropdown-link "> AMA </a> </li> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/Community-Led-Events/bd-p/adapt" class="nav-dropdown-link "> Community Led Events </a> </li> <li class="nav-dropdown-item"> <a href="https://www.hubspot.com/resources/webinar" class="nav-dropdown-link nav-external-link" target="_blank"> Webinars </a> </li> <li class="nav-dropdown-item"> <a href="https://www.hubspot.com/hubspot-user-groups" class="nav-dropdown-link nav-external-link" target="_blank"> HUGS </a> </li> </ul> </div> </div> </li> <li class="nav-group-item nav-group-item-has-dropdown"> <div class="nav-link-wrapper"> <a class="nav-link forum"> <span class="nav-link-label"> Advocacy </span> <span class="dropdown-caret"></span> </a> <div class="nav-popover-arrow"></div> <div class="nav-popover forum"> <ul class="nav-dropdown-list"> <li class="nav-dropdown-item"> <a href='/t5/Advocacy/ct-p/advocacy' class="nav-dropdown-link nav-hubFans-program "> Community Champions Program </a> </li> <li class="nav-dropdown-item"> <a href="/t5/Advocates-Blog/bg-p/advocates-blog" class="nav-dropdown-link nav-adovcates-blog "> Champions Blog </a> </li> </ul> </div> </div> </li> </ul> <div class="login-container v2"> <style> #lia-body .community-header-nav.v2 .forum-nav-bar.ch-sticky .nav-menu > ul.nav-group-primary { transform: none !important; } #lia-body .community-header-nav.v2 .forum-nav-bar.ch-sticky .login-container.v2 { display: flex; align-items: center; gap: 12px; padding-left: 0; width: 116px; } #lia-body.lia-user-status-anonymous .community-header-nav.v2 .forum-nav-bar.ch-sticky .login-container.v2 { gap: 0; width: 24px; } .ch-sticky .nav-menu { gap: 36px; } .custom-search-focus { background-image: url('https://community.hubspot.com/html/@C11BAC294B66222FECF4AA35890ACE71/assets/search-icon.svg'); background-repeat: no-repeat; background-size: contain; cursor: pointer; margin: 0; width: 24px; height: 24px; } </style> <label class="custom-search-focus"></label> <div class="nav-link-wrapper custom-user-menu-v2"> <div class="nav-popover-arrow"></div> <div class="nav-popover profile first"> <div class="header-dropdown-menu"> <div class="user-heading">Anonymous</div> <ul class="header-tab-nav"> <li><span id="profile" class="active">Profile</span></li> </ul> <div class="header-tab-nav-content"> <div id="profile-list-wrapper"> <div class="nav-link-wrapper"> <a href="https://app.hubspot.com/l/reports-dashboard/" class="text-link my-account nav-hubspot-account" target="_blank"> Go to my HubSpot Account </a> </div> <ul class="nav-dropdown-list"> <li class="nav-dropdown-item"> <a href="/t5/user/viewprofilepage/user-id/-1" class="nav-dropdown-link nav-account-profile-2"> My Profile </a> </li> <li class="nav-dropdown-item"> <a href="/t5/user/myprofilepage/tab/personal-profile" class="nav-dropdown-link nav-account-user-settings"> Settings </a> </li> <li class="nav-dropdown-item"> </li> <li class="nav-dropdown-item"> <a href="https://community.hubspot.com/t5/community/page.logoutpage?t:cp=authentication/contributions/unticketedauthenticationactions&dest_url=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2FCommunity-Champion-Opportunities%2FShare-your-review-of-Adobe-Express%2Fba-p%2F1062774&lia-action-token=0EzzK19Ykl5ThTT31jvtDbgaxTcUGM51l_5wX0U8sxM.&lia-action-token-id=logoff" class="nav-dropdown-link nav-account-sign-out"> Sign out </a> </li> </ul> </div> <div id="admin-list-wrapper" style="display: none;"> <div class="admin-menu-list"> </div> </div> </div> </div> </div> </div> </div> </div> </div> </nav> </div> </div> </section> </div> </div> </div><div class="lia-quilt-row lia-quilt-row-footer"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-common-footer lia-mark-empty"> </div> </div> </div> <!-- hs.custom-banner-forumpage --> <style> .lia-img-message-type-solved:after{ content: "Solved"; } .lia-img-message-type-solution:after{ content: 'Solved'; } </style> <section class="custom-hero-banner-v2 page-full-width" style="background-image:url('/html/assets/CRM-banner.png');"> <div class="custom-v2-banner"> <div class="custom-v2-banner__announcement"><div class="custom-announcement__title"> <div class="custom-v2-banner__announcement"> <div class="community-banner-message transparent"> <div class="banner-message-inner-wrapper"> <div class="hsg-promo-bar__wrapper" data-background="gradient4"> <div class="hsg-page-width-normal" data-background-image="circle2"> <div class="hsg-promo-bar" id="promo-bar"> </div> </div> </div> </div> </div> </div> <style> .lia-page { overflow-x: hidden; } .custom-v2-banner__announcement:not(:has(.transparent)) { margin-bottom: -20px; transform: translateY(-20px); } .CommunityPage .custom-v2-banner__announcement:not(:has(.transparent)) { margin-bottom: -72px; transform: translateY(-72px); } #promo-bar { background: #c1eaeb; border: 0; padding: 16px 0; position: relative; } #promo-bar:before, #promo-bar:after { background: #c1eaeb; content: ''; width: 300%; position: absolute; top: 0; bottom: 0; left: 0; } #promo-bar:before { transform: translateX(-100%); } #promo-bar:after { left: auto; right: 0; transform: translateX(100%); } #promo-bar .banner-message-text { display: flex; gap: 16px; align-items: center; justify-content: center; font-size: 16px !important; font-weight: 300; line-height: 1.75; text-align: center; } #promo-bar .banner-message-text .banner-cta-link { margin: 0; text-align: center; } @media only screen and (max-width: 767px) { #promo-bar .banner-message-wrapper:not(:first-child) { display: none; } #promo-bar .banner-message-text { flex-direction: column; gap: 8px; } #promo-bar .banner-message-text .banner-cta-link { min-width: 50%; } } @media only screen and (max-width: 1023px) { .custom-v2-banner__announcement:not(:has(.transparent)) { margin-bottom: -30px !important; transform: translateY(-30px) !important; } } </style> <script> const userLang = 'en'; const injectContent = (results) => { let promoBar = document.querySelector('#promo-bar'); // glowingblue: Remove stage test announcements if there are real ones if ( results.length ) { promoBar.replaceChildren(); } results.forEach(result => { if (result.message.length) { if (document.querySelector('.community-banner-message.transparent')) { document.querySelector('.community-banner-message.transparent').classList.remove('transparent'); } let messageWrapper = document.createElement('div'); messageWrapper.classList.add('banner-message-wrapper'); let message = document.createElement('p'); message.classList.add("banner-message-text"); message.innerHTML = result.message.trim(); promoBar.appendChild(messageWrapper); messageWrapper.appendChild(message) if (result.ctaLink.length && result.ctaText.length) { let ctaWrapper = document.createElement('div'); let cta = document.createElement('a'); ctaWrapper.classList.add('banner-cta-link'); cta.classList.add('banner-cta-link'); cta.setAttribute('href',result.ctaLink.trim()); cta.innerHTML = result.ctaText.trim(); message.appendChild(cta); } } else { return; } }) } const processBanner = (results) => { if (sessionStorage.getItem("HSCommunityAnnouncements")) { let data = JSON.parse(sessionStorage.getItem('HSCommunityAnnouncements')); if (data.lang === userLang) { data.messages = results; sessionStorage.setItem("HSCommunityAnnouncements", JSON.stringify(data)); return; } else { injectContent(results); data.lang = userLang; data.messages = results; sessionStorage.setItem("HSCommunityAnnouncements", JSON.stringify(data)); } } else { injectContent(results); messageData = { lang: userLang, messages: results } sessionStorage.setItem("HSCommunityAnnouncements", JSON.stringify(messageData)); } } if (sessionStorage.getItem('HSCommunityAnnouncements')) { let data = JSON.parse(sessionStorage.getItem('HSCommunityAnnouncements')); if (userLang === data.lang) { injectContent(data.messages); } } </script> <script src="https://script.google.com/a/hubspot.com/macros/s/AKfycbySpmgzEjP1bEnrLD5xM_fdMOxDn47Kt9buJO9HtQER7pZmhu0/exec?lang=en&callback=processBanner" async> </script> </div></div> <div class="custom-v2-banner__wrapper"> <div class="page-breadcrumb-wrapper"> <div aria-label="breadcrumbs" role="navigation" class="BreadCrumb crumb-line lia-breadcrumb lia-component-common-widget-breadcrumb"> <ul role="list" class="lia-list-standard-inline"> <li class="lia-breadcrumb-node crumb"> <a href="/" class="crumb-community lia-breadcrumb-community lia-breadcrumb-forum community-home">HubSpot Community</a> </li> <li class="lia-breadcrumb-node crumb"> <a href="https://community.hubspot.com/t5/Advocacy/ct-p/advocacy" class="crumb-category lia-breadcrumb-category lia-breadcrumb-forum">Advocacy</a> </li> <li class="lia-breadcrumb-node crumb"> <a href="https://community.hubspot.com/t5/Community-Champion-Opportunities/bg-p/community-champion-opportunities" class="crumb-board lia-breadcrumb-board lia-breadcrumb-forum">Community Champion Opportunities</a> </li> <li class="lia-breadcrumb-node crumb final-crumb"> <span>Share your review of Adobe Express</span> </li> </ul> </div> </div> <div class="page-title-wrapper"> <h2 class="page-title">Community Champion Opportunities</h2> </div> <div class="custom-v2-banner__search"> <div class="seach-prefix"> Search Community Champion Opportunities for solutions or ask a question </div> <div class="custom-search-wrapper"><div id='lia-searchformV32_2349c97941611b' class='SearchForm lia-search-form-wrapper lia-mode-default lia-component-common-widget-search-form'> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='ajaxfeedback_2349c97941611b'></div> </div> <div id='searchautocompletetoggle_2349c97941611b'> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='ajaxfeedback_2349c97941611b_0'></div> </div> <form enctype='multipart/form-data' class='lia-form lia-form-inline SearchForm' action='https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.form.form' method='post' id='form_2349c97941611b' name='form_2349c97941611b'><div class='t-invisible'><input value='blog-id/community-champion-opportunities/article-id/55' name='t:ac' type='hidden'></input><input value='search/contributions/page' name='t:cp' type='hidden'></input><input value='3Y-X2iSO5byqEtIaPVfKjKBTKeQfynUgAZJwM0-2knSYzpbE4-g0ShHwnTogeUB8sg8XW3uKtY0KPx-1LdOMqiNj9hsZtAbRW-69qdB2m0mn2FTt3FpnkQ81KJJSJa7g11FSqVycbbS2hE500TKQxIBef7vOWYcKhnu-ojJOltz6CZDPDZCkh5NsC8VfY1oQMM-OREpKu9bsbuY8L_z-T2L76jCx1YimC65gBCatf3tHGHdIoxc8wwFAQtxzSlpIc7roGG6s3NaMErWfOfznvg7cFO4kd6kct_j19OXQeExqtWed4IKXb8dBwN8--p-bCUJhzo8t3I0SXF99UTko4w4wlelNaRzHqw6uc6QnK-4JJeCwn-zlFwsBVFmKxxk8KvAIkAkChlb-RHSOXx6VT4TfHPEP_UDr2TxquO_omV42WqUky7UHf3HVPK2spYOEv5GryCRy3-VjQbrJ0Ez-j5rG7kQVYAzGQng6UZVPBOG3Iq3Mod7m4ARXcfzWc7u59bfFbAzV5IhXWPtktAYUhncLaxsJtRyxc8uPWCNWmH2_BoDiyt_I79JV4k7PuNHx79WRoLJIlXiaVABC24QJSPSRwUuh2GpQJ1wyP8nJBRniE8FqBOxsQQ_iW15rbJsdJ8c4iZ5WOXl-ZSndfKjvMNojIb4ShCuLV1v7cJKBs0kX7Ay_PIt6KQqH7mZq7euntfh2e4dlvsQxjn7iDVm0GBGnoq5Jsh4Lk0ZsNXf4v6H7uyj_kSMbbNgFpfNGB-z7P4o2g0akk6nGukh1GZaTFQ9JvOYoXRs1rfD95RdEj8RvSViADNS3ufmr4LY3KLj5z6hSuMKq9F2pVPgNoBLpqzcBw37nfIAhsYPyQoB1v9hH61INriaEMFaC5nr6ZbgHl2WbuY0r8MWj9ZgXp9gRekK9AnW8optyuLTRbNSlOHhiyCTIROPm8PLmDvoNfhKlWVWFVU6t0ugMbOfrNCxtfoF1ptcOUd0pqDv5Za0kf4xmsTkMg16n666OxTnPe1ZSxmFylCR1S-JO7EAlSk5g5Tu60k_P6eTSNiAUfLqs1w1nd2QIschiv6vcdCVwaN7L3mWtN1Or6i-Ipkyt9eZQuOOYq1VZdsY2C73zEHT5sVdtdv232TUlTXhlMyt_IaQ8u5LMNJ45shkuqYXe4l7mnWJY5_EZ3e5paDJPQz-IEevciStWqfbIGMsqHfEimMPD3CpAeKjztVmsklt6LY2O4BuY9TQhyQmbNRJTk6EueUg.' name='lia-form-context' type='hidden'></input><input value='BlogArticlePage:blog-id/community-champion-opportunities/article-id/55:searchformv32.form:' name='liaFormContentKey' type='hidden'></input><input value='5DI9GWMef1Esyz275vuiiOExwpQ=:H4sIAAAAAAAAALVSTU7CQBR+krAixkj0BrptjcpCMSbERGKCSmxcm+kwlGrbqTOvFDYexRMYL8HCnXfwAG5dubDtFKxgYgu4mrzvm3w/M+/pHcphHQ4kI4L2dMo9FLYZoM09qbeJxQ4V0+XC7e/tamqyBPEChwgbh1JAjQtLIz6hPaYh8ZlEMaxplAvm2KZmEsm0hhmBhOKpzZzOlsEw8LevR5W3zZfPEqy0oJIYc+eCuAyh2rolfaI7xLN0I8rjWfWBj7CuzJvf5osmbxRN3hacMimNwHRtKSOr0XNnv/vx+FoCGPjhMRzljhNLYHrEt9kA5T08ACCsKvREoYuqxqLl8BLO84q4UcMITcG49y/QOGs1pYyESl5p6V6qwRW086rinVmoxMZsiZud/zBUTc6gmVc4kExkJafmcYG1GM9+wfIsCkf2OP54hal5EjnG54z8h0XhjfcF7wQUs5Kz0GTjU2rOjc/llTT4Au07pDOcBQAA' name='t:formdata' type='hidden'></input></div> <div class='lia-inline-ajax-feedback'> <div class='AjaxFeedback' id='feedback_2349c97941611b'></div> </div> <input value='_vE_BERkXiR5pJRzz51vUCR9FU8znoK-0IoRm_MEIoQ.' name='lia-action-token' type='hidden'></input> <input value='form_2349c97941611b' id='form_UIDform_2349c97941611b' name='form_UID' type='hidden'></input> <input value='' id='form_instance_keyform_2349c97941611b' name='form_instance_key' type='hidden'></input> <span class='lia-search-granularity-wrapper'> <select title='Search Granularity' class='lia-search-form-granularity search-granularity' aria-label='Search Granularity' id='searchGranularity_2349c97941611b' name='searchGranularity'><option title='All Results' value='mjmao93648|community'>All Results</option><option title='This forum' value='advocacy|category'>This forum</option><option title='Blog' selected='selected' value='community-champion-opportunities|blog-board'>Blog</option><option title='Users' value='user|user'>Users</option></select> </span> <span class='lia-search-input-wrapper'> <span class='lia-search-input-field'> <span class='lia-button-wrapper lia-button-wrapper-secondary lia-button-wrapper-searchForm-action'><input value='searchForm' name='submitContextX' type='hidden'></input><input class='lia-button lia-button-secondary lia-button-searchForm-action' value='Search' id='submitContext_2349c97941611b' name='submitContext' type='submit'></input></span> <input placeholder='Search the Community' aria-label='Search' title='Search' class='lia-form-type-text lia-autocomplete-input search-input lia-search-input-message' value='' id='messageSearchField_2349c97941611b_0' name='messageSearchField' type='text'></input> <input placeholder='Search the Community' aria-label='Search' title='Search' class='lia-form-type-text lia-autocomplete-input search-input lia-search-input-tkb-article lia-js-hidden' value='' id='messageSearchField_2349c97941611b_1' name='messageSearchField_0' type='text'></input> <input ng-non-bindable='' title='Enter a user name or rank' class='lia-form-type-text UserSearchField lia-search-input-user search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a user name or rank' value='' id='userSearchField_2349c97941611b' name='userSearchField' type='text'></input> <input placeholder='Enter a keyword to search within the private messages' title='Enter a search word' class='lia-form-type-text NoteSearchField lia-search-input-note search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a search word' value='' id='noteSearchField_2349c97941611b_0' name='noteSearchField' type='text'></input> <input title='Enter a search word' class='lia-form-type-text ProductSearchField lia-search-input-product search-input lia-js-hidden lia-autocomplete-input' aria-label='Enter a search word' value='' id='productSearchField_2349c97941611b' name='productSearchField' type='text'></input> <input class='lia-as-search-action-id' name='as-search-action-id' type='hidden'></input> </span> </span> <span class='lia-cancel-search'>cancel</span> </form> <div class='search-autocomplete-toggle-link lia-js-hidden'> <span> <a class='lia-link-navigation auto-complete-toggle-on lia-link-ticket-post-action lia-component-search-action-enable-auto-complete' data-lia-action-token='LZJnv-23FcBIho4toPgOWREPwqWiWyCGD6mOQEvaRbE.' rel='nofollow' id='enableAutoComplete_2349c97941611b' href='https://community.hubspot.com/t5/blogs/v2/blogarticlepage.enableautocomplete:enableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions'>Turn on suggestions</a> <span class='HelpIcon'> <a class='lia-link-navigation help-icon lia-tooltip-trigger' role='button' aria-label='Help Icon' id='link_2349c97941611b' href='#'><span class='lia-img-icon-help lia-fa-icon lia-fa-help lia-fa' alt='Auto-suggest helps you quickly narrow down your search results by suggesting possible matches as you type.' aria-label='Help Icon' role='img' id='display_2349c97941611b'></span></a><div role='alertdialog' class='lia-content lia-tooltip-pos-bottom-left lia-panel-tooltip-wrapper' id='link_2349c97941611b_0-tooltip-element'><div class='lia-tooltip-arrow'></div><div class='lia-panel-tooltip'><div class='content'>Auto-suggest helps you quickly narrow down your search results by suggesting possible matches as you type.</div></div></div> </span> </span> </div> </div> <div class='spell-check-showing-result'> Showing results for <span class='lia-link-navigation show-results-for-link lia-link-disabled' aria-disabled='true' id='showingResult_2349c97941611b'></span> </div> <div> <span class='spell-check-search-instead'> Search instead for <a class='lia-link-navigation search-instead-for-link' rel='nofollow' id='searchInstead_2349c97941611b' href='#'></a> </span> </div> <div class='spell-check-do-you-mean lia-component-search-widget-spellcheck'> Did you mean: <a class='lia-link-navigation do-you-mean-link' rel='nofollow' id='doYouMean_2349c97941611b' href='#'></a> </div> </div></div> </div> </div> </section> </div> </div> </div><div class="lia-quilt-row lia-quilt-row-main"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-main-content"> <div class="lia-quilt-column-alley lia-quilt-column-alley-single"> <div class="blog-article-page-body template-centered template-section"> <div class="body"> <div class="lia-menu-bar top-block lia-component-menu-bar"> <div class="lia-decoration-border-menu-bar"> <div class="lia-decoration-border-menu-bar-top"> <div> </div> </div> <div class="lia-decoration-border-menu-bar-content"> <div> <div class="lia-menu-bar-buttons"> <div class="lia-menu-navigation-wrapper lia-js-hidden lia-menu-action" id="actionMenuDropDown_2349c979c96046"> <div class="lia-menu-navigation"> <div class="dropdown-default-item"><a title="Show option menu" class="lia-js-menu-opener default-menu-option lia-js-click-menu lia-link-navigation" aria-expanded="false" role="button" aria-label="Options" id="dropDownLink_2349c979c96046" href="#">Options</a> <div class="dropdown-positioning"> <div class="dropdown-positioning-static"> <ul aria-label="Dropdown menu items" role="list" id="dropdownmenuitems_2349c979c96046" class="lia-menu-dropdown-items"> <li role="listitem"><a class="lia-link-navigation rss-thread-link lia-component-rss-action-thread" rel="nofollow noopener noreferrer" id="rssThread_2349c979c96046" href="/mjmao93648/rss/message?board.id=community-champion-opportunities&message.id=55">Subscribe to RSS Feed</a></li> <li aria-hidden="true"><span class="lia-separator lia-component-common-widget-link-separator"> <span class="lia-separator-post"></span> <span class="lia-separator-pre"></span> </span></li> <li role="listitem"><span class="lia-link-navigation mark-thread-unread lia-link-disabled lia-component-forums-action-mark-thread-unread" aria-disabled="true" id="markThreadUnread_2349c979c96046">Mark as New</span></li> <li role="listitem"><span class="lia-link-navigation mark-thread-read lia-link-disabled lia-component-forums-action-mark-thread-read" aria-disabled="true" id="markThreadRead_2349c979c96046">Mark as Read</span></li> <li aria-hidden="true"><span class="lia-separator lia-component-common-widget-link-separator"> <span class="lia-separator-post"></span> <span class="lia-separator-pre"></span> </span></li> <li role="listitem"><span class="lia-link-navigation addThreadUserBookmark lia-link-disabled lia-component-subscriptions-action-add-thread-user-bookmark" aria-disabled="true" id="addThreadUserBookmark_2349c979c96046">Bookmark</span></li> <li role="listitem"><span class="lia-link-navigation addThreadUserEmailSubscription lia-link-disabled lia-component-subscriptions-action-add-thread-user-email" aria-disabled="true" id="addThreadUserEmailSubscription_2349c979c96046">Subscribe</span></li> <li aria-hidden="true"><span class="lia-separator lia-component-common-widget-link-separator"> <span class="lia-separator-post"></span> <span class="lia-separator-pre"></span> </span></li> <li role="listitem"><a class="lia-link-navigation print-article lia-component-forums-action-print-thread" rel="nofollow" id="printThread_2349c979c96046" href="/t5/blogs/blogarticleprintpage/blog-id/community-champion-opportunities/article-id/55">Printer Friendly Page</a></li> <li role="listitem"><a class="lia-link-navigation report-abuse-link lia-component-forums-action-report-abuse" rel="nofollow" id="reportAbuse_2349c979c96046" href="/t5/notifications/notifymoderatorpage/message-uid/1062774">Report Inappropriate Content</a></li> </ul> </div> </div> </div> </div> </div> </div> </div> </div> <div class="lia-decoration-border-menu-bar-bottom"> <div> </div> </div> </div> </div><div data-lia-message-uid="1062774" id="messageview_2349c97a66c7d3" class="lia-panel-message message-uid-1062774 lia-component-article"> <div data-lia-message-uid="1062774" class="lia-message-view-wrapper lia-js-data-messageUid-1062774 lia-component-forums-widget-message-view-two" id="messageView2_1_2349c97a66c7d3"> <span id="U1062774"> </span> <span id="M55"> </span> <div class="lia-inline-ajax-feedback"> <div class="AjaxFeedback" id="ajaxfeedback_2349c97a66c7d3"></div> </div> <div class="MessageView lia-message-view-blog-topic-message lia-message-view-display lia-row-standard-unread lia-thread-topic"> <span class="lia-message-state-indicator"></span> <div class="lia-quilt lia-quilt-blog-topic-message lia-quilt-layout-blog-topic-message-layout"> <div class="lia-quilt-row lia-quilt-row-header"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-title-header lia-mark-empty"> </div> </div><div class="lia-quilt-row lia-quilt-row-sub-header"> <div class="lia-quilt-column lia-quilt-column-12 lia-quilt-column-left lia-quilt-column-sub-header-left"> <div class="lia-quilt-column-alley lia-quilt-column-alley-left"> <div class="author-info-wrapper"> <div class="author-avatar"> <div class="lia-message-author-avatar lia-component-author-avatar lia-component-message-view-widget-author-avatar"><div class="UserAvatar lia-user-avatar lia-component-common-widget-user-avatar"> <img class="lia-user-avatar-message" title="gaherrera" alt="gaherrera" id="imagedisplay_2349c97a838af2" src="/t5/image/serverpage/image-id/111425i2E8B16C22B7E3C57/image-dimensions/38x38/image-coordinates/586%2C240%2C1490%2C1144/constrain-image/false?v=v2"/> </div></div> </div> <div class="author-name"> <span class='author-username-wrapper'> <span class="post-written-by">by: </span> <span class='UserName lia-user-name lia-user-rank-HubSpot-Employee lia-component-message-view-widget-author-username'> <a class='lia-link-navigation lia-page-link lia-user-name-link' style='' target='_self' aria-label='View Profile of gaherrera' itemprop='url' id='link_2349c97a9136aa' href='https://community.hubspot.com/t5/user/viewprofilepage/user-id/435700'><span class=''>gaherrera</span></a> <img class='lia-user-rank-icon lia-user-rank-icon-right' title='HubSpot Employee' alt='HubSpot Employee' id='display_0_2349c97a9136aa' src='https://cdn2.hubspot.net/hubfs/98485/community.hubspot.com/Sprocket%20(Employee).png'/> </span> </span><div title="Posted on" class="lia-message-post-date lia-component-post-date lia-component-message-view-widget-post-date"> <span class="DateTime"> <span title="Nov 1, 2024 9:00 AM" class="local-friendly-date"> 3 weeks ago </span> </span> </div><div class="lia-message-author-rank lia-component-author-rank lia-component-message-view-widget-author-rank"> HubSpot Employee </div> </div> </div> </div> </div><div class="lia-quilt-column lia-quilt-column-12 lia-quilt-column-right lia-quilt-column-sub-header-right"> <div class="lia-quilt-column-alley lia-quilt-column-alley-right"> <div class="date-and-share-wrapper full-width-flex"> <div class="share-and-options"> <div class="lia-menu-navigation-wrapper lia-js-hidden lia-menu-action lia-component-message-view-widget-action-menu" id="actionMenuDropDown_2349c97b40cddc"> <div class="lia-menu-navigation"> <div class="dropdown-default-item"><a title="Show option menu" class="lia-js-menu-opener default-menu-option lia-js-click-menu lia-link-navigation" aria-expanded="false" role="button" aria-label="Show Share your review of Adobe Express post option menu" id="dropDownLink_2349c97b40cddc" href="#"></a> <div class="dropdown-positioning"> <div class="dropdown-positioning-static"> <ul aria-label="Dropdown menu items" role="list" id="dropdownmenuitems_2349c97b40cddc" class="lia-menu-dropdown-items"> <li role="listitem"><a class="lia-link-navigation rss-thread-link lia-component-rss-action-thread" rel="nofollow noopener noreferrer" id="rssThread_2349c97b40cddc" href="/mjmao93648/rss/message?board.id=community-champion-opportunities&message.id=55">Subscribe to RSS Feed</a></li> <li aria-hidden="true"><span class="lia-separator lia-component-common-widget-link-separator"> <span class="lia-separator-post"></span> <span class="lia-separator-pre"></span> </span></li> <li role="listitem"><span class="lia-link-navigation mark-thread-unread lia-link-disabled lia-component-forums-action-mark-thread-unread" aria-disabled="true" id="markThreadUnread_2349c97b40cddc">Mark as New</span></li> <li role="listitem"><span class="lia-link-navigation mark-thread-read lia-link-disabled lia-component-forums-action-mark-thread-read" aria-disabled="true" id="markThreadRead_2349c97b40cddc">Mark as Read</span></li> <li aria-hidden="true"><span class="lia-separator lia-component-common-widget-link-separator"> <span class="lia-separator-post"></span> <span class="lia-separator-pre"></span> </span></li> <li role="listitem"><span class="lia-link-navigation addThreadUserBookmark lia-link-disabled lia-component-subscriptions-action-add-thread-user-bookmark" aria-disabled="true" id="addThreadUserBookmark_2349c97b40cddc">Bookmark</span></li> <li role="listitem"><span class="lia-link-navigation addThreadUserEmailSubscription lia-link-disabled lia-component-subscriptions-action-add-thread-user-email" aria-disabled="true" id="addThreadUserEmailSubscription_2349c97b40cddc">Subscribe</span></li> <li aria-hidden="true"><span class="lia-separator lia-component-common-widget-link-separator"> <span class="lia-separator-post"></span> <span class="lia-separator-pre"></span> </span></li> <li role="listitem"><a class="lia-link-navigation print-article lia-component-forums-action-print-thread" rel="nofollow" id="printThread_2349c97b40cddc" href="/t5/blogs/blogarticleprintpage/blog-id/community-champion-opportunities/article-id/55">Printer Friendly Page</a></li> <li role="listitem"><a class="lia-link-navigation report-abuse-link lia-component-forums-action-report-abuse" rel="nofollow" id="reportAbuse_2349c97b40cddc" href="/t5/notifications/notifymoderatorpage/message-uid/1062774">Report Inappropriate Content</a></li> </ul> </div> </div> </div> </div> </div> </div> <div class="date"> <div title="Posted on" class="lia-message-post-date lia-component-post-date lia-component-message-view-widget-post-date"> <span class="DateTime"> <span title="Nov 1, 2024 9:00 AM" class="local-friendly-date"> 3 weeks ago </span> </span> </div> </div> </div> </div> </div> </div><div class="lia-quilt-row lia-quilt-row-body"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-body"> <div class="lia-quilt-column-alley lia-quilt-column-alley-single"> <h1 class="PageTitle lia-component-common-widget-page-title"><span class="lia-link-navigation lia-link-disabled" aria-disabled="true" id="link_2349c97a66c7d3">Share your review of Adobe Express</span></h1> <div class="lia-message-body-wrapper lia-component-message-view-widget-body"> <div itemprop="text" id="bodyDisplay_2349c97a66c7d3" class="lia-message-body"> <div class="lia-message-body-content"> <P>Calling all content creators!<SPAN> With HubSpot's native integration, you can use Adobe’s AI content creation app to design and edit high-quality assets to perfection. Make stunning images and designs to use in social posts, marketing collateral, emails, and more with Adobe Express.</SPAN></P> <P> </P> <P>We want to hear about your experience and use cases. Share your review on the App Marketplace. Don't forget to reply here with a link to your review to receive full points for this opportunity.<BR /><BR /></P> <!--HubSpot Call-to-Action Code --> <P><SPAN class="hs-cta-wrapper"><SPAN class="hs-cta-node hs-cta-502003fb-47aa-405a-9f22-1e4e38ebbe17"><!-- [if lte IE 8]><div id="hs-cta-ie-element"></div><![endif]--><A href="https://cta-redirect.hubspot.com/cta/redirect/53/502003fb-47aa-405a-9f22-1e4e38ebbe17" target="_blank" rel="noopener noreferrer"><IMG style="border-width: 0px;" class="hs-cta-img" src="https://no-cache.hubspot.com/cta/default/53/502003fb-47aa-405a-9f22-1e4e38ebbe17.png" border="0" alt="Write a review" /></A><BR /><EM>Having trouble loading the page? <A href="https://ecosystem.hubspot.com/marketplace/apps/adobe-express" target="_self" rel="noopener noreferrer">Click here instead.</A></EM><BR /></SPAN></SPAN></P> <!-- end HubSpot Call-to-Action Code --> </div> </div> </div> <div id="labelsWithEvent_2349c97a66c7d3" class="LabelsForArticle lia-component-labels lia-component-message-view-widget-labels-with-event"> <span aria-level="5" role="heading" class="article-labels-title"> Select a label to view existing ideas by category:: </span> <div class="LabelsList"> <ul role="list" id="list_2349c97a66c7d3" class="lia-list-standard-inline"> <li class="label"> <a class="label-link lia-link-navigation lia-custom-event" rel="tag" id="link_2349c97a66c7d3_0" href="/t5/Community-Champion-Opportunities/bg-p/community-champion-opportunities/label-name/brand%20endorsement">Brand Endorsement<wbr /></a> </li> </ul> </div> </div> </div> </div> </div><div class="lia-quilt-row lia-quilt-row-footer"> <div class="lia-quilt-column lia-quilt-column-12 lia-quilt-column-left lia-quilt-column-footer-left"> <div class="lia-quilt-column-alley lia-quilt-column-alley-left"> <div class="lia-button-group lia-component-comment-button lia-component-message-view-widget-comment-button"> </div> </div> </div><div class="lia-quilt-column lia-quilt-column-12 lia-quilt-column-right lia-quilt-column-footer-right"> <div class="lia-quilt-column-alley lia-quilt-column-alley-right"> <div data-lia-kudos-id="1062774" id="kudosButtonV2_2349c97a66c7d3" class="KudosButton lia-button-image-kudos-wrapper lia-component-kudos-widget-button-version-3 lia-component-kudos-widget-button-horizontal lia-component-kudos-widget-button lia-component-kudos-action lia-component-message-view-widget-kudos-action"> <div class="lia-button-image-kudos lia-button-image-kudos-horizontal lia-button-image-kudos-enabled lia-button-image-kudos-not-kudoed lia-button"> <div class="lia-button-image-kudos-count"> <span class="lia-link-navigation kudos-count-link lia-link-disabled" aria-disabled="true" title="The total number of upvotes this post has received." id="link_0_2349c97a66c7d3"> <span itemprop="upvoteCount" id="messageKudosCount_2349c97bd53291" class="MessageKudosCount lia-component-kudos-widget-message-kudos-count"> 0 </span><span class="lia-button-image-kudos-label lia-component-kudos-widget-kudos-count-label"> Upvotes </span> </span> </div> <div class="lia-button-image-kudos-give"> <a onclick="return LITHIUM.EarlyEventCapture(this, 'click', true)" class="lia-link-navigation kudos-link lia-link-ticket-post-action" role="button" data-lia-kudos-entity-uid="1062774" aria-label="Click here to upvote this post." title="Click here to upvote this post." data-lia-action-token="SY09tzPrGUXP1-bqNJOhKAuVWcOe7Hz5pjG4CNOBLxQ." rel="nofollow" id="kudoEntity_2349c97a66c7d3" href="https://community.hubspot.com/t5/blogs/v2/blogarticlepage.kudosbuttonv2.kudoentity:kudoentity/kudosable-gid/1062774?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=kudos/contributions/tapletcontributionspage"> </a> </div> </div> </div> </div> </div> </div> </div> </div> </div> <div class="lia-progress lia-js-hidden" id="progressBar_2349c97a66c7d3"> <div class="lia-progress-indeterminate"></div> </div> </div><div class="lia-menu-bar lia-discussion-page-discussion-navigator lia-component-article-navigator"> <div class="lia-decoration-border-menu-bar"> <div class="lia-decoration-border-menu-bar-top"> <div> </div> </div> <div class="lia-decoration-border-menu-bar-content"> <div> <div class="lia-menu-bar-buttons"> </div> <div class="lia-paging-full-wrapper" id="threadnavigator_2349c97c17afc6"> <ul class="lia-paging-full"> <li class="lia-paging-page-previous lia-paging-page-listing lia-component-listing"> <a class="lia-link-navigation" title="Community Champion Opportunities" id="link_2349c97c17afc6" href="/t5/Community-Champion-Opportunities/bg-p/community-champion-opportunities"> <span class="lia-paging-page-arrow"></span> <span class="lia-paging-page-link">Back to Blog</span> </a> </li> <li class="lia-paging-page-previous lia-component-previous"> <a class="lia-link-navigation" title="What's your experience with HubSpot's Clip Creator?" id="link_0_2349c97c17afc6" href="/t5/Community-Champion-Opportunities/What-s-your-experience-with-HubSpot-s-Clip-Creator/ba-p/1063423"> <span class="lia-paging-page-arrow"></span> <span class="lia-paging-page-link">Next Blog post</span> </a> </li> <li class="lia-paging-page-next lia-component-next"> <a class="lia-link-navigation" title="What's your experience with HubSpot's Meeting Scheduler?" id="link_1_2349c97c17afc6" href="/t5/Community-Champion-Opportunities/What-s-your-experience-with-HubSpot-s-Meeting-Scheduler/ba-p/1063429"> <span class="lia-paging-page-link">Previous Blog post</span> <span class="lia-paging-page-arrow"></span> </a> </li> </ul> </div> </div> </div> <div class="lia-decoration-border-menu-bar-bottom"> <div> </div> </div> </div> </div><div class="comment-section-wrapper"> <div class="comment-section template-centered template-section"> <a name="comment-on-this"></a> <span id="feedback-successinformationbox_0_2349c97da67c2e"> </span> <div class="InfoMessage lia-panel-feedback-banner-note lia-component-comment-editor" id="informationbox_0_2349c97da67c2e"> <div role="alert" class="lia-text"> <p ng-non-bindable="" tabindex="0"> You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in. </p> <ul role="list" id="list_2349c97da67c2e" class="lia-list-standard"> <li><a class="lia-link-navigation blog-link lia-message-comment-post" rel="nofollow" id="link_2349c97da67c2e" href="https://app.hubspot.com/khoros/integration/jwt/authenticate?redirectreason=permissiondenied&referer=https%3A%2F%2Fcommunity.hubspot.com%2Ft5%2FCommunity-Champion-Opportunities%2FShare-your-review-of-Adobe-Express%2Fba-p%2F1062774%23comment-on-this">Comment</a></li> </ul> </div> </div> </div> </div> </div> <div class="sidebar"> <div class="lia-panel lia-panel-standard BlogLatestArticlesTaplet Chrome lia-component-blogs-widget-latest-articles"><div class="lia-decoration-border"><div class="lia-decoration-border-top"><div> </div></div><div class="lia-decoration-border-content"><div><div class="lia-panel-heading-bar-wrapper"><div class="lia-panel-heading-bar"><span aria-level="3" role="heading" class="lia-panel-heading-bar-title">Latest blog posts</span></div></div><div class="lia-panel-content-wrapper"><div class="lia-panel-content"><div id="bloglatestarticlestaplet_2349c97db4eb73" class="BlogLatestArticlesTaplet"> <ul role="list" id="list_2349c97db4eb73" class="lia-list-standard"> <li class="even-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73" href="/t5/Community-Champion-Opportunities/Share-your-video-testimonial-of-HeyGen/ba-p/1065329">Share your video testimonial of HeyGen</a> </li> <li class="odd-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_0" href="/t5/Community-Champion-Opportunities/Discuss-the-latest-news-from-Masters-in-Marketing/ba-p/1065322">Discuss the latest news from Masters in Marketing</a> </li> <li class="even-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_1" href="/t5/Community-Champion-Opportunities/What-s-your-experience-with-HubSpot-s-Clip-Creator/ba-p/1063423">What's your experience with HubSpot's Clip Creator?</a> </li> <li class="odd-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_2" href="/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774">Share your review of Adobe Express</a> </li> <li class="even-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_3" href="/t5/Community-Champion-Opportunities/What-s-your-experience-with-HubSpot-s-Meeting-Scheduler/ba-p/1063429">What's your experience with HubSpot's Meeting Scheduler?</a> </li> <li class="odd-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_4" href="/t5/Community-Champion-Opportunities/Can-you-answer-these-questions-on-the-HubSpot-Community/ba-p/1063403">Can you answer these questions on the HubSpot Community?</a> </li> <li class="even-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_5" href="/t5/Community-Champion-Opportunities/Submit-a-Quote-for-the-HubSpot-Blog/ba-p/1063385">Submit a Quote for the HubSpot Blog</a> </li> <li class="odd-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_6" href="/t5/Community-Champion-Opportunities/Share-your-review-of-Amplitude/ba-p/1062759">Share your review of Amplitude</a> </li> <li class="even-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_7" href="/t5/Community-Champion-Opportunities/Discover-how-AI-is-revolutionising-marketing/ba-p/1063257">Discover how AI is revolutionising marketing</a> </li> <li class="odd-row"> <a class="lia-link-navigation" id="articleLink_2349c97db4eb73_8" href="/t5/Community-Champion-Opportunities/Essential-Apps-for-Sales-2024/ba-p/1062269">Essential Apps for Sales 2024</a> </li> </ul> </div></div></div></div></div><div class="lia-decoration-border-bottom"><div> </div></div></div></div><div class="lia-component-recommendations-widget-recommended-content-taplet-loader lia-component-lazy-loader lia-lazy-load lia-component-recommendations-widget-recommended-content-taplet" id="lazyload"></div> </div> </div> <style> #lia-body .lia-component-recommendations-widget-recommended-content-taplet td .verified-icon .lia-img-message-type-solved::after{ content: "Solved"; } </style> </script> </div> </div> </div><div class="lia-quilt-row lia-quilt-row-footer"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-common-footer"> <div class="lia-quilt-column-alley lia-quilt-column-alley-single"> <div class="lia-quilt lia-quilt-footer lia-quilt-layout-one-column lia-component-quilt-footer"> <div class="lia-quilt-row lia-quilt-row-header"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-common-header lia-mark-empty"> </div> </div><div class="lia-quilt-row lia-quilt-row-main"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-main-content"> <div class="lia-quilt-column-alley lia-quilt-column-alley-single"> <!-- FOOTER START --> <footer class="community-footer"> <div class="community-footer-wrapper template-centered"> <div class="footer-responsive"> <div class="contain-top"> <div class="col"> <h5>HubSpot</h5> <ul> <li><a href="https://www.hubspot.com/">Home</a></li> <li><a href="https://help.hubspot.com/">Help</a></li> <li><a href="https://academy.hubspot.com/">Academy</a></li> <li><a href="https://knowledge.hubspot.com/">Knowledge Base</a></li> <li><a href="https://ecosystem.hubspot.com/marketplace/solutions">Solutions Directory</a></li> <li><a href="https://blog.hubspot.com/">Blog</a></li> </ul> </div> <div class="col"> <h5>Get involved</h5> <ul> <li><a href= "https://offers.hubspot.com/community-champions" class="">Community Champions</a> </li> <li><a href= "https://www.hubspot.com/hubspot-user-groups" class="">HubSpot User Groups</a></li> <li><a href= "https://www.hubspot.com/partners/solutions" class="">Solutions Partner Program</a></li> <li><a href= "https://www.hubspot.com/community-newsletter" class="">Community Newsletter</a></li> </ul> </div> <div class="col"> <h5>Community</h5> <ul> <li><a href= "https://community.hubspot.com/t5/CRM-Sales-Hub/ct-p/sales">CRM & Sales</a></li> <li><a href= "https://community.hubspot.com/t5/Marketing-Hub/ct-p/marketing">Marketing</a></li> <li><a href= "https://community.hubspot.com/t5/Service-Hub/ct-p/service_hub">Service</a></li> <li><a href= "https://community.hubspot.com/t5/RevOps-Operations/ct-p/Operations">RevOps & Operations</a></li> <li><a href= "https://community.hubspot.com/t5/HubSpot-Developers/ct-p/developers" class="">Developers</a></li> <li><a href= "https://community.hubspot.com/t5/Getting-Started-on-the-Community/How-to-join-the-Solutions-Partner-Program/ba-p/400205" class="">Partners</a></li> </ul> </div> <div class="col"> <ul> <li><a href= "https://community.hubspot.com/t5/Academy/ct-p/academy">Academy</a></li> <li><a href= "https://community.hubspot.com/t5/Groups/ct-p/groups">Groups</a></li> <li><a href= "/t5/Advocacy/ct-p/advocacy" class="">Advocacy</a></li> <li><a href= "/t5/HubSpot-Ideas/idb-p/HubSpot_Ideas" class="">Ideas</a></li> </ul> </div> </div> <div class="contain-top-two"> <hr class="seperator"> </div> </div> <div class="footer-main-two"> <div class="footer-copywrite-container"> <a href="/" id="footer-logo" class="footer-wordmark"> <img src="https://community.hubspot.com/html/@B5A74D0D426EC31D4B4C76F0526FF1E5/assets/HS_Logo_Wordmark-White.svg" alt="hubspot"> </a> <span id="copywrite">Copyright © 2024 HubSpot, Inc.</span> </div> <div class="footer-links-container"> <ul class="footer-link"> <li> <a href="https://legal.hubspot.com/privacy-policy" class="footer-terms">Privacy Policy</a> </li> <li> <a href="https://legal.hubspot.com/community-tou" class="footer-privacy">Community Terms of Use</a> </li> <li> <a href="https://community.hubspot.com/t5/Getting-Started-on-the-Community/HubSpot-Community-Guidelines/ba-p/384050" class="footer-guidelines">Community Guidelines</a> </li> <li> <a href="https://status.hubspot.com/" class="footer-status">Status</a> </li> <li> <a href="https://legal.hubspot.com/digital-services-act" class="footer-dsa">DSA Statement</a> </li> <li class="hs-footer-cookie-settings footer-cookie-settings" hidden> <a href=""></a> </li> </ul> </div> </div> </div> </footer> </div> </div> </div><div class="lia-quilt-row lia-quilt-row-footer"> <div class="lia-quilt-column lia-quilt-column-24 lia-quilt-column-single lia-quilt-column-common-footer lia-mark-empty"> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> <script> document.cookie = "Crowdvocate_user_ck=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; //console.log('deleting cookie for logged out case'); //console.log('is cookie set? '+ getCookie('Crowdvocate_user_ck')); </script> <!-- Start HubSpot SDK Script --> <script>window.acsdk = {cid: 'WSUMXJFRDOXTWNTJ8YKRUEQ6GHNP1UAUP9EA', params: {popupDelay: 10, position: [0, 20, 50, 0, 0]}};</script> <script>(function(){var w=window;var ac=w.Crowdvocate;if(typeof ac==="function"){ac('init',w.acsdk);}else{var a=function(){a.c(arguments)};a.q=[];a.c=function(args){a.q.push(args)};w.Crowdvocate=a;} var s=document.createElement('script');s.type='text/javascript';s.async=true;s.src='https://d29zub39v1xeg4.cloudfront.net/api/v1/sdk.js';var x=document.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);})();</script> <!-- End of HubSpot SDK Script --> </center> </div> <script type="text/javascript"> new Image().src = ["/","b","e","a","c","o","n","/","1","2","5","3","6","9","2","3","7","4","1","1","_","1","7","3","2","3","9","0","5","4","6","0","2","6",".","g","i","f"].join(""); </script> <script type="text/javascript" src="/t5/scripts/C1D0FDEB5D557CE5FA1EFA105E95A13F/lia-scripts-common-min.js"></script><script type="text/javascript" src="/t5/scripts/96493E13CDBF4CDCDE8B7B6C0744BC64/lia-scripts-body-min.js"></script><script language="javascript" type="text/javascript"> <!-- LITHIUM.Sandbox.restore(); LITHIUM.jQuery.fn.cssData.defaults = {"dataPrefix":"lia-js-data","pairDelimeter":"-","prefixDelimeter":"-"}; (function($){ jQuery(document).on('click', '#hs-eu-confirmation-button', function() { location.reload(); }); })(LITHIUM.jQuery); LITHIUM.CommunityJsonObject.User.policies['forums.action.message-view.batch-messages.allow'] = false; // <script> // $ prefix because 'core' is kinda prone to conflict with other stuff that might define a variable 'core' // If there are issues with markup_output (on communities that have auto-escaping enabled!) you can add // ?no_esc behind the expression, but can't have that conditionally there because FreeMarker will just throw // an exception just because the built in is in the code if auto-escaping is disabled... var $core = { config: {"initialized" : true,"debug" : false,"devmode" : false,"env" : "prod","context" : "component","versions" : {"lithium" : 24.8,"freemarker" : "2.3.26-incubating","core.component" : 23.8,"core.users" : 23.5,"core" : 23.4},"file" : "core.cmp.noscript","output" : "undefined","locale" : {"charset" : "UTF-8","timezone" : "US/Eastern","format" : {"date" : "MMM d, yyyy","time" : "h:mm a","full" : "MMM d, yyyy h:mm a","relative" : true,"cutoff" : 31}},"lang" : "en","langs" : ["en","de","es","fr","ja","pt-br"],"node" : {"id" : "community-champion-opportunities","uid" : 960,"lang" : "en","type" : "board","style" : "blog","url" : "https://community.hubspot.com/t5/Community-Champion-Opportunities/bg-p/community-champion-opportunities","quilt" : "BlogArticlePage","skin" : "hubspot","path" : "\/boards/id/community-champion-opportunities","top" : "\/categories/id/hubspot_community_en","settings_key" : "config_node"},"user" : {"admin" : false,"mod" : false,"auth" : false,"device" : "desktop","lang" : "en","registered" : false,"roles" : [],"show_text_keys" : false,"settings_key" : "config_user","settings" : {}},"cache" : {"cached" : false,"type" : "","map" : {},"config" : {"usercache" : {"inactiveTime" : 1200000,"maximumTime" : 7200000,"maxSize" : 10000},"appcache" : {"inactiveTime" : 1200000,"maximumTime" : 7200000,"maxSize" : 10000}}},"modules" : {"api" : false,"community" : false,"component" : true,"nodes" : false,"templates" : false,"users" : true},"ids" : {"lang" : {"en" : "hubspot_community_en","de" : "hubspot_community_de","es" : "hubspot_community_es","fr" : "hubspot_community_fr","ja" : "hubspot_community_jp","pt" : "hubspot_community_pt"}},"request" : {"get" : "","uri" : "https://community.hubspot.com/t5/Community-Champion-Opportunities/Share-your-review-of-Adobe-Express/ba-p/1062774","type" : "GET","https" : true,"options" : {},"context" : "","endpoint" : "https://community.hubspot.com/mjmao93648/plugins/custom/hubspot/hubspot/controller"},"rest_default_version" : 1,"rest_base_url_v1" : "https://community.hubspot.com/restapi/v1","rest_base_url_v2" : "https://community.hubspot.com/api/2.0/search?q=","stats" : {"requests" : 0,"total" : 0,"calls" : []},"logs" : []}, properties: {}, // Add a reference to the global (native) JS object for the community, can be useful lithium: LITHIUM?.CommunityJsonObject, version: LITHIUM?.CommunityJsonObject?.Config?.['app.revision'] || null }; // </script> // <script> just for inline syntax-highlighting... ;(function($){ var tools = { admin: { /** /* By default admin/studio pages all have the same page title "Community Settings" which is extermely unhelpful /* when having 10 admin/studio tabs open and then having to cycle through them just to find the right one. /* This fix makes admin/studio tabs more distinguishable. /*/ fixStudioTitle: function() { var title = [ $('.lia-bizapps-tab-studio-tab-group .lia-tabs-active').text(), $('.lia-bizapps-tab-community-tab-group .lia-tabs-active').text(), $('.lia-bizapps-page-title-community').text(), ]; $('title').text(title.join(' - ')); }, /** /* In multi-language communities it can be difficult to distinguish nodes if they all have the same title but another language. /* Usually we add a language suffix to the node ID but that is not easily visible in the community structure. Thea idea of this /* enhancement is to extract that language suffix from the node id and add it to the node title within the community structure. /*/ structureAddLanguageFromID: function(validLanguages = ['de', 'fr', 'it', 'jp', 'en', 'es', 'pt']) { $('.lia-component-admin-widget-node-editor-tree .lia-list-tree-toggle-node').each(function() { var $listitem = $(this); var $title = $listitem.find('span.lia-node-display-node-title:first'); var id = $listitem.find('.manage-node-link').first().prop('href').split('_').pop().trim().toLowerCase(); // console.log(id); if ( validLanguages.includes(id) ) { $title.text($title.text() + ' (' + id.toUpperCase() + ')'); } }); }, }, dom: function() { /** /* Just a little clutter saver for components. We should specify both aria-label and title /* attributes, but doing so can lead to very messy markup. As aria-label is more important /* devs have the option of simply adding an empty title attribute as well to elements which /* have aria-label already. This little tool will look for those and simply copy the aria-label /* text over to the title attribute so mouse-users can also get the hints as tooltips. /*/ $('[aria-label][title=""]').each(function() { $(this).attr('title', $(this).attr('aria-label')); }); /** /* A 'big-target' implementation I came up with using a `data-target` attribute on the actual link. /*/ (function(attr = 'data-target') { document.querySelectorAll(`a[${attr}]`).forEach(link => { //console.log('handling big target', link); let trigger = link.parentNode; const selector = link?.getAttribute(attr); while (trigger && trigger !== document) { if ( (!selector || trigger.matches(selector)) && !trigger.matches('[data-edit], [data-bind]') ) { trigger.style.cursor = 'pointer'; trigger.querySelectorAll('a').forEach(link => link.addEventListener('click', (e) => e.stopPropagation())); trigger.addEventListener('click', (e) => (!window.getSelection().toString() && link.click())); break; } trigger = trigger.parentNode; } }); })(); }, installKV: function() { /** /* Creates a Deno KV-like key-value store based on the localStorage with optional /* (custom) versioning and migration support. /* This has passed basic testing, although not how well it aligns with what native Deno KV does itself! /* The API is the same minus atomic operations and some options like consistency level, Kv64 etc. /* that don't really make sense or are insanely complex or impossible to do with a synchronous /* API like localStorage is. The KV's methods are all fake async (because Deno KV's api is async) /* so code written and used in the browser with this implementation should (hopefully) work /* with Deno KV on the server side as well (muuuch more testing needed to confirm that)! /* Aside from `open()` and `close()` this implementation should cover the entire Deno KV API. /* /* There are of course differences in behavior you should be aware of if you build any logic around it: /* - The most important limitation with this mock is that if you use non-serializable values in /* in your keys (your values too!), it will not behave like you expect! Stick to types that can be /* serialized to JSON... localStorage can only handle strings, that's the reason. /* - With Deno KV you can pass an `expireIn` option, but the values you receive from `get()` /* will never return that expiration date. This implementation returns that information /* when querying a key, e.g. besides value and versionstamp you get back `expires` /* if a TTL was provided when setting the value, it's going to be an ISO timestamp otherwise `null`. /* - `list()` is an AsyncIterator like it is with Deno KV, but it does NOT support cursor, consistency /* or batchSize options, you can pass them to the method, but nothing will happen. /* Deno KV `list()` requires a selector, this implementation does not, it will simply list all /* KV entries if you don't provide a selector. /* Furthermore this `list()` implementation supports an optional 3rd argument which is a function /* passed to `Array.filter()` that allows filtering the returned entries further after selector(s) /* and options have been applied. /* - This implementation does support some features Deno KV does not have: /* 1. You can optionally provide a `version` function that will replace how the versionstamp is created /* and thus provide your own versioning implementation. /* 2. You can also provide a `migrate` function that will be applied if an outdated versionstamp /* is encountered, you can for example extend the old value with some new ones keeping what was stored. /* 3. There is a `toJSON()` method you can use to serialize either the entire KV store or a portion of it. /* It supports the same arguments as `list()` (because it internally calls it) and has options to /* serialize prettified JSON or streamable (individual store entries separated by newlines) JSON. /* /* @param {Function} [version] - Optional function to generate a versionstamp for stored values. /* @param {Function} [migrate] - Optional function to migrate outdated values. It is called with the key and outdated value and should return the migrated value. /* @param {String} [prefix='kv:'] - Prefix to be used for keys in localStorage, helping in namespacing and avoiding key conflicts. /* /* @returns {Object} - An object providing the mocked Deno KV API methods to interact with the store. /*/ Object.defineProperties(window, { $kv: { value: function(version, migrate, prefix = 'kv:') { const $hash = typeof version === 'function' ? version : ( $hash || ((v) => 1) ); const _queueListeners = new Set(); const _key = { inrange: (key, start, end) => { const orderedKey = _key.order(key); const orderedStart = start ? _key.order(start) : null; const orderedEnd = end ? _key.order(end) : null; if (orderedStart && orderedKey < orderedStart) { return false; } if (orderedEnd && orderedKey >= orderedEnd) { return false; } return true; }, order: (key) => key.slice().sort((a, b) => { const order = ['Uint8Array', 'string', 'number', 'bigint', 'boolean']; const typeA = typeof a, typeB = typeof b; return typeA === typeB ? (typeA === 'number' ? a - b : String(a).localeCompare(String(b))) : order.indexOf(typeA) - order.indexOf(typeB); }), // Check how Deno KV actually does it here: // https://github.com/denoland/deno/blob/main/ext/kv/codec.rs serialize: (key) => prefix + JSON.stringify(key), deserialize: (serializedKey) => JSON.parse(serializedKey.substring(prefix.length)) }; return { /** /* Retrieves a value from the store by its key(s). /* /* @param {Array} key - The keys array to look up the value. /* /* @returns {Promise<Object>} - An object containing the key, its associated value, versionstamp and expiry. /*/ get: async (key) => { const serializedKey = _key.serialize(key); const data = JSON.parse(localStorage.getItem(serializedKey)); if ( !data || (data.expires && new Date(data.expires) < new Date()) ) { data && data.expires && localStorage.removeItem(serializedKey); return { key, value: null, versionstamp: null }; } if ( data.versionstamp !== $hash(data.value) ) { if (typeof migrate === 'function') { data.value = await migrate(key, data.value); data.versionstamp = $hash(data.value); localStorage.setItem(serializedKey, JSON.stringify(data)); } else { console.warn('Outdated value found but no migration function defined!', key, data); } } return { key, ...data }; }, /** /* Retrieves multiple values from the store based on the provided keys. /* /* @param {Array<Array>} keys - An array of key arrays to look up the values. /* /* @returns {Promise<Array<Object>>} - An array of objects. /*/ getMany: async function(keys) { return Promise.all(keys.map(key => this.get(key))) }, /** /* Stores a value in the store with the given key. /* /* @param {Array} key - The key array to associate with the value. /* @param {*} value - The value to be stored. /* @param {Object} [options] - Optional parameters for storing the value. `expireIn` sets the expiration time in milliseconds. /* /* @returns {Promise<Object>} - An object indicating the success and the versionstamp of the stored value. /*/ set: async (key, value, options = {}) => { const data = { value, versionstamp: $hash(value), expires: options.expireIn ? new Date(Date.now() + options.expireIn).toISOString() : null }; localStorage.setItem(_key.serialize(key), JSON.stringify(data)); return { ok: true, versionstamp: data.versionstamp }; }, /** /* Removes a value from the store by its key. /* /* @param {Array} key - The key array of the value to be removed. /*/ delete: async (key) => localStorage.removeItem(_key.serialize(key)), /** /* Iterates over values in the store based on the provided selector prefix, range, or limited by options. /* This method provides an AsyncIterator to be used with `for await...of` loops. /* This `list()` implementation supports an additional third argument which is NOT STANDARD for Deno KV. /* It's a function that allows for further filtering the results before yielding them within the iterator. /* /* The selector can either be a prefix selector or a range selector: /* - A prefix selector selects all keys that start with the given prefix (optionally starting at a given key). /* - A range selector selects all keys that are lexicographically between the given start and end keys. /* /* @param {Object} [selector] - The selection criteria. NOT STANDARD: Can be undefined here, not with Deno KV! /* @property {Array} [selector.prefix] - Defines the prefix for filtering the results. /* @property {Array} [selector.start] - The starting key for range selection. /* @property {Array} [selector.end] - The ending key for range selection. /* @param {Object} [options] - Optional parameters for the listing. /* @property {number} [options.limit] - Limits the number of results. /* @property {boolean} [options.reverse] - If true, reverses the order of results. /* @param {Function} [fn] - NON STANDARD: Optional filter function to filter entries before yielding. /* /* @returns {AsyncIterator} - An AsyncIterator yielding store entries. /*/ list: async function*(selector, options = {}, fn) { const keys = options?.reverse ? Object.keys(localStorage).reverse() : Object.keys(localStorage); for (const serializedKey of keys) { // Limit results based on `options.limit` if ( options?.limit !== undefined && options.limit <= 0 ) { break; } if ( !serializedKey.startsWith(prefix) ) { continue; }; const deserializedKey = _key.deserialize(serializedKey); if ( selector?.prefix && !deserializedKey.slice(0, selector.prefix.length).every((part, index) => part === selector.prefix[index]) ) { continue; } if ( selector?.start && !_key.inrange(deserializedKey, selector?.start, selector?.end) ) { continue; } const entry = await this.get(deserializedKey); if ( entry.value !== null ) { if ( !fn || (typeof fn === 'function' && fn(entry)) ) { if ( options?.limit !== undefined ) { options.limit--; } yield entry; } } } }, /** /* Adds a value into a mock database queue to be delivered to queue listeners. /* This method simulates the behavior of the Deno KV's enqueue(). /* `keysIfUndelivered` option is not supported, you can pass it, but won't have an effect. /* /* @param {*} value - The value to be enqueued. /* @param {Object} [options] - Optional settings for the enqueue operation. /* @param {number} [options.delay] - Delays the delivery of the value by the specified number of milliseconds. /* @returns {Promise<Object>} - An object indicating the success of the enqueue operation. /*/ enqueue: async (value, options) => { for (const fn of _queueListeners) { options?.delay && await (new Promise(res => setTimeout(res, options.delay))); await fn(value); }; }, /** /* Listens for queue values to be delivered from the mock database queue. /* This method simulates the behavior of the Deno KV's listenQueue(). /* /* @param {Function} handler - A callback function that gets when a new value is dequeued. /* /* @returns {Promise<void>} /*/ listenQueue: async (handler) => { if ( !_queueListeners.has(handler) ) { _queueListeners.add(handler) } }, /** /* Provides a mock for Deno Kv's AtomicOperation API with the same chainable methods, but /* they do nothing...you can provide a function to each of the mock methods that receives /* `this`, just return it again, otherwise you break the chaining! Something like this: /* /* @example /* ``` /* (await $core.kv().atomic()).check((t) => (console.log('check'), t)).min((t) => (console.log('min'), t)).commit(); /* // you can pass an arbitrary number of additional args to those mock functions, like /* (await $core.kv().atomic()).check((t, arg1, arg2) => (console.log('check', arg1, arg2), t), 'foo', 'var') /* ``` /* /* @returns {Promise<*>} - A mocked API of Deno KV's atomic(). /*/ atomic: async () => { console.warn('Atomic ops are not supported!'); return [ 'check', 'commit', 'delete', 'enqueue', 'max', 'min', 'mutate', 'set', 'sum' ].reduce((r, m) => (r[m] = function(fn, ...args) { return typeof fn === 'function' ? fn(this, ...args) : this }, r), Object.create(null)); }, /** /* NON STANDARD: Serializes the store to a JSON string. /* The methods first 3 arguments match the ones from list() (see there for details). /* /* @param {Object} selector - The selection criteria for list(). /* @param {Object} [options] - Optional parameters for list(). /* @param {Function} [fn] - Optional filter function list(). /* @param {Number|String} [pretty] - JSON.stringify() space param to prettify output. Defatuls to tab indent. /* @param {Boolean} [streamable] - If the output should be stringified entries separated by newlilnes. /* /* @returns {String} - A JSON string representation of the KV store. /*/ toJSON: async function(selector, options, fn, pretty = '\t', streamable = false) { let data = []; for await (const entry of this.list(selector, options, fn)) { data.push(entry); } data = data.reduce((r, entry) => { if ( streamable ) { r.push(JSON.stringify({ key: entry.key, value: entry.value })); return r; } return r[JSON.stringify(entry.key)] = entry.value, r; }, streamable ? [] : {}); return streamable ? data.join('\n') : JSON.stringify(data, null, pretty); }, }; }, configurable: false, // Cannot be deleted enumerable: false, // Will not show up in loops writable: false, // Cannot be overwritten } }); }, logMeIn: function() { /** /* Enable 'magic' redirect to login page when "logmein" is typed into the void =) /* just for convenience and speed, only useful on stage if SSO is activated for the community /*/ var keycodes = { logmein: [76, 79, 71, 77, 69, 73, 78], listudio: [], liadmin: [] }; var neededkeys = [76, 79, 71, 77, 69, 73, 78]; var watching = false; var count = 0; $(document).keydown(function(e) { var key = e.keyCode; // console.log(key); // Set start to true only if the first key in the sequence is pressed if ( !watching ) { if ( key == neededkeys[0] ) { watching = true; } } // If watching, pay attention to key presses, looking for right sequence. if ( watching ) { // console.log('watching: ' + key); if ( neededkeys[count] == key ) { // We're good so far. count++; } else { // Oops, not the right sequence, lets restart from the top. watching = false; count = 0; return; } if ( count == neededkeys.length ) { // We made it! Execute whatever should happen when entering the right sequence window.location.replace('/t5/user/userloginpage'); // Reset the conditions so that someone can do it all again. watching = false; count = 0; return; } } else { // Oops. watching = false; count = 0; return; } }); }, scssCompile: function() { /** /* Handle on-the-fly SCSS compilation if any text/scss inline style tags are found /* This primarily useful for development, no style/scss tags should remain when on /* production as they would still be compiled. /*/ if ( $('style[type$="scss"]').length ) { $('style[type$="scss"]').each(function() { var $el = $(this); var scss = $el.text(); $core?.config?.user?.admin && console.log('Found inline SCSS style tag, compiling to CSS...', $el); $.ajax({ type: 'POST', url: 'https://www.sassmeister.com/app/lib/compile', data: { input: scss, compiler: 'lib', syntax: 'scss', original_syntax: 'scss', output_style: 'nested' }, contentType: 'multipart/form-data', dataType: 'json' }) .done(function(response) { $core?.config?.user?.admin && console.log('core.cmp.tools: SCSS successfully compiled, injecting usable CSS...'); // inject compiled CSS into irignal source tag $el.text(response.css).attr('type', 'text/css'); }) .fail(function(err) { console.log('fail', err); }); }); } }, redirect: function() { /** /* Handles redirects specified in `cmp.global.scripts` (or elsewhere) and added to /* the `$core.redirects` object. /*/ if ( $core?.redirects ) { Object.entries($core.redirects).some(function([source, target]) { if ( window.location.pathname === source ) { $core?.config?.user?.admin && console.log('core.cmp.tools: redirect match found, redirecting...'); window.location.href = target; // Stop the .some() iteration by returning true return true; } }); } }, fixAmp: function() { /** /* Handle & ampersand bug in SEO field of ArticleEditorPage (TKB, more?) /* & gets replace with & but each time the article is saved again, the & of the & /* gets encoded again, resulting in repeated and invalid encoding, e.g. &amp;amp;amp;amp; /*/ $('.lia-form-message-seo-description-input, .lia-form-message-seo-title-input').each(function() { $(this).val($(this).val().replaceAll(/amp;/gm, '')); }); }, fixTOC: function() { /** /* Adds 'is--toc' class to `ul`-tag of BlogArticle TOC (table of contents, an Angular component by Khoros) /* as it natively does not have any identifier, making it tricky to target with CSS /* WebKit browsers remove list semantics when list-style-type is none, we fix that with `role=list` /*/ $('.BlogArticlePage a[href*="#toc-hId"]').first().parents('ul').addClass('is--toc').attr('role', 'list'); }, inview: function() { // Make sure nothing explodes in older browsers that do not support IntersectionObserver if ( 'IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype ) { // Minimal polyfill for Edge 15's lack of `isIntersecting` // See: https://github.com/w3c/IntersectionObserver/issues/211 if ( !('isIntersecting' in window.IntersectionObserverEntry.prototype) ) { Object.defineProperty(window.IntersectionObserverEntry.prototype, 'isIntersecting', { get: function () { return this.intersectionRatio > 0; } }); } // Set up the intersection observer const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { // If the element is fully in view, trigger custom event with jQuery if (entry.isIntersecting && entry.intersectionRatio === 1) { entry.target.dataset.inview = 'true'; $(document).trigger('inview', [entry.target]); } else if ( entry.target.dataset.inview !== 'false' ) { entry.target.dataset.inview = 'false'; } }); }, { threshold: 1 }); // Observe each element with attribute `data-inview` $('[data-inview]').each(function() { observer.observe(this); }); } }, XHRmiddleware: function() { // Middleware storage const middlewares = []; // Override the XMLHttpRequest constructor (breaks Khoros for some reason...) /* const xhrRequest = XMLHttpRequest; XMLHttpRequest = function() { const xhr = new xhrRequest(); return new Proxy(xhr, { get: function(target, prop) { // You can intercept specific properties here and provide custom values if ( prop === 'responseText' ) { let value = target[prop]; middlewares.forEach((fn) => { if ( !fn.on || fn.on.includes('response') { // make sure in case the dev forgets to return from the fn() // we still return a value, it's gonna be the unmodified one // but better than nothing... value = fn(target, 'response', value) || value; } }); return value; } // For all other properties, return the original value return target[prop]; }, set: function(target, prop, value) { target[prop] = value; return true; } }); }; XMLHttpRequest.prototype = xhrRequest.prototype; */ // Save original XHR methods const xhrSend = XMLHttpRequest.prototype.send; const xhrOpen = XMLHttpRequest.prototype.open; // Define the .$use() method using Object.defineProperty Object.defineProperty(XMLHttpRequest, '$use', { value: function(fn) { if ( typeof fn === 'function' ) { middlewares.push(fn); } }, writable: false, // Cannot be overwritten enumerable: false, // Will not show up in loops configurable: false, // Cannot be deleted }); // Override the .open() method XMLHttpRequest.prototype.open = function(method, url, async, user, password) { middlewares.forEach((fn) => { !fn.on || fn.on.includes('open') ? fn(this, 'open', method, url, async, user, password) : null; }); xhrOpen.apply(this, arguments); }; // Override the .send() method XMLHttpRequest.prototype.send = function(body) { const xhr = this; middlewares.forEach((fn) => { !fn.on || fn.on.includes('send') ? fn(xhr, 'send', body) : null; }); // Attach an event listener for the 'readystatechange' event xhr.addEventListener('readystatechange', function() { if ( xhr.readyState === XMLHttpRequest.DONE ) { middlewares.forEach((fn) => { !fn.on || fn.on.includes('done') ? fn(xhr, 'done') : null; }); } }); xhrSend.apply(this, arguments); }; // Example middleware /* XMLHttpRequest.$use((xhr, event, ...args) => { if ( event === 'open' ) { // args is going to be [method, url, async, user, password] if defined console.log('Request created:', xhr, ...args); } else if ( event === 'send' ) { // args is going to be [body] the optional payload/body of the request console.log('Request payload is about to be sent:', args[0]); } else if ( event === 'response' ) { // args is going to be [responseText]! console.log('Can modify response text!, xhr); // IMPORTANT: Return something from here, // otherwise response is gonna be returned umodified to the caller! return xhr; } else if ( event === 'done' ) { console.log('Response received:', xhr.responseText); } }); */ }, }; // Install tools before extending core in case we need access to globally defined tools! Object.entries(tools).map(function([name, fn]) { if ( typeof fn === 'function' ) { $core?.config?.user?.admin && console.info(`core.cmp.tools: Running tools.${name}()`); fn(); } }); // Extend $core base variable with additional capabilities if ( $core ) { /** /* Helper method to automatically proxy requests to external URL's through the proxy endpoint /* when they can't be requested directly via JS due to CORS restrictions. /*/ $core.compile = async function(mount) { console.time("compiling"); // start benchmark // Handles inline tag definitions and script-linked tags with `[type="riot/tag"]`. // Why not go with the native riot.compile()? a) we want to check first if we actually // need to fetch the tag file or not (if it wasn't modified) and b) we want to compile // tags that are defined inline as well and cache the compiled result of both for faster // loading during prototyping. const usedb = true; const db = localStorage; const sources = Array.prototype.slice.call(document.querySelectorAll('script[type*="riot"]')).concat(Array.prototype.slice.call(document.querySelectorAll('template[type*="riot"]'))); const tags = await Promise.all( sources.map(async (el, i, arr) => { //console.log('compiling from:', el.hasAttribute('data-src') ? 'File' : 'Inline Template'); let cached; let hash; let tag; let response; if (el.hasAttribute('data-src')) { try { if ( usedb ) { // first we want to check the last modified header! // if the dev server is not running this will fail and enter catch block response = await fetch(el.getAttribute('data-src'), { // credentials: 'include', method: 'HEAD', }); // typecast to string for localStorage (keys are strings!) hash = `riot:${await $hash(el.getAttribute('data-src') + new Date(response.headers.get('last-modified')).getTime())}`; cached = db.getItem(hash); } if ( !cached ) { console.log('compile(): No cached version found, fetching source!'); response = await fetch(el.getAttribute('data-src'), { // credentials: 'include', method: 'GET', }); data = await response.json(); response = { headers: [...response.headers].reduce((acc, header) => { return { ...acc, [header[0]]: header[1] }; }, {}), status: response.status, data: data, }; //console.log('response', response); // add attribute data-scoped="false" to the include script tag to turn off CSS scoping //tag = riot.compileFromString(response.data, { scopedCss: !['false', '0', 0].includes(el.getAttribute('data-scoped')) }).code; // TODO: Dev server should return compiled code exactly as riot.deno.dev does tag = response.data?.code; //console.log('tag', tag); if ( tag && usedb ) { db.setItem(hash, tag); } console.log(`compile(): No cached tag found, file-tag compiled ${usedb ? `and cached with hash ${hash}`: ''}`); } else { tag = cached; console.log(`compile(): Found cached version of file-tag!`); } } catch (ex) { // We do not get details for net errors (e.g. if the server is down), so we try to // isolate those because they don't have a message (well, which ones do? custom ones?) if ( !ex.data?.message ) { console.warn(`compile(): fetching ${el.getAttribute('data-src')} failed: Dev server is not reachable...`); } else { console.error(ex); } } } // if `tag` is still undefined, we can assume the dev server wasn't running, // so we compile the content locally if ( !tag ) { // `.innerHTML` returns the 'fixed' (browser interpreted) HTML, but it will encode // `&` to `&`, also within riot expressions, which of course messes up the compiler // so once we have the tag html, we need to get those encoded ampersands back to normal tag = el.innerHTML.replace(/&/g, '&').trim(); if ( !tag ) { console.warn(`compile(): Tried to get content from inline tag, but was empty, did you forget to paste the component code in?`, el); return; } hash = `riot:${await $hash(tag)}`; cached = db.getItem(hash); if ( !cached ) { // add attribute data-scoped="false" to the template tag to turn off CSS scoping //tag = riot.compileFromString(tag, { scopedCss: !['false', '0', 0].includes(el.getAttribute('data-scoped')) }).code; try { response = await (await fetch('https://riot.deno.dev', { method: 'POST', body: JSON.stringify({ markup: encodeURIComponent(tag), versionstamp: await $hash(tag), key: await $hash(window.location.origin), }), headers: { 'content-type': 'application/json', }, })).json(); if ( response.code ) { tag = response.code; } else { console.error('compile(): Faulty response', response); if ( response.payload ) { console.warn('compile(): Creating downloadable file from failed component markup!'); const file = new File([response.payload.markup], 'failed.tag.html', { type: 'text/plain', }); const fr = new FileReader(); fr.onload = function(e) { const link = `<span class="compile-error">Download uncompilable markup: <a href="${URL.createObjectURL(file)}" download="${file.name}">${file.name}</a></span>`; $('body').append(link); } fr.readAsText(file); } throw new Error('compile(): response.code could not be found!', { cause: 'fetch()', message: response }); } } catch(ex) { console.error(ex); tag = null; } if ( tag && usedb ) { db.setItem(hash, tag); } console.log(`compile(): No cached tag found, inline-tag compiled ${usedb ? `and cached with hash ${hash}` : ''}`); } else { tag = cached; console.log(`compile(): Found cached version of inline-tag!`); } } try { const { groups: { name = null } } = tag.match(/^riot\.register\(['"](?<name>[^\s'"]+)/) || { groups: {} }; console.log('compile(): tag name = ', name); //riot.inject(tag, name, `./${name}.html`); // yeahyeah, eval is evil, but we are the author of the code, so nothing to worry... } catch (ex) { console.error('compile(): Something went wrong with tag name extraction', ex); } try { eval(tag); } catch(ex) { console.error(`compile(): Something went wrong with tag evaluation for '${name}'`, ex); } return { name: name, hash: hash, cached: cached !== null, code: tag }; }) ); console.log('compile(): result', tags); console.timeEnd('compiling'); // end benchmark // optionally mount tag(s) via the compile() method if ( typeof mount === 'string' ) { console.time('mounting'); riot.mount(mount); console.timeEnd('mounting'); } else if ( typeof mount === 'boolean' ) { // auto-mount all top level components, but not the nested ones, they will be handled by the parent riot.mount('[is]:not([is] [is])'); } }; /** /* Helper method to automatically proxy requests to external URL's through the proxy endpoint /* when they can't be requested directly via JS due to CORS restrictions. /* /* @usage `$core.fetch('<url>', {<fetch.options>});` /* /* @param {string} url - The URL to fetch via proxy. /* @param {object} options - An optional fetch options object. /* /* @returns {any} - The proxied response data. /*/ $core.fetch = function(url = '', options = {}, cache) { return fetch(`${$core.config.request.endpoint}?get=proxy&url=${encodeURIComponent(url)}${cache ? '&cache=' + cache : ''}`, options); }; $core.fmt = { /** /* Adapted from https://github.com/lukeed/tinydate/blob/master/src/index.js /* /* @usage `$core.fmt.date('Current time: [{HH}:{mm}:{ss}]')(new Date())` /* /* @param str - Output string with placeholders /* @param custom - Custom formatter functions for placeholders (optional) /* /* @return - Returns a rendering function that will optionally accept a date value as its only argument. /*/ date: function(str, custom) { const RGX = /([^{]*?)\w(?=\})/g; const MAP = { YYYY: 'getFullYear', YY: 'getYear', MM: function (d) { return d.getMonth() + 1; }, DD: 'getDate', HH: 'getHours', mm: 'getMinutes', ss: 'getSeconds', fff: 'getMilliseconds' }; let parts=[], offset=0; str.replace(RGX, function(key, _, idx) { // save preceding string parts.push(str.substring(offset, idx - 1)); offset = idx += key.length + 1; // save function parts.push(custom && custom[key] || function(d) { return ('00' + (typeof MAP[key] === 'string' ? d[MAP[key]]() : MAP[key](d))).slice(-key.length); }); }); if ( offset !== str.length ) { parts.push(str.substring(offset)); } return function(arg) { var out='', i=0, d=arg||new Date(); for (; i<parts.length; i++) { out += (typeof parts[i]==='string') ? parts[i] : parts[i](d); } return out; }; }, }; /** /* Map (now) global $hash function to $core namespace for backwards compatibility. /*/ $core.hash = $hash; /** /* Dynamically imports and appends scripts to the DOM. /* Offers extended functionality such as manual deferral, error handling, and initialization tasks. /* /* @usage /* ``` /* $import('path/to/script.js').then(() => { /* console.log('All scripts loaded!'); /* }).catch(error => { /* console.error('Error loading script:', error); /* }); /* ``` /* /* @param {String|Array|HTMLElement|NodeList} input - Path(s) to the script(s) to be imported, or DOM nodes. /* @param {Object} [options] - Optional configuration object. /* @param {Function} [options.on] - Function to manually handle the script injection. /* @param {Function} [options.init] - Function to run initial tasks, e.g. for setup purposes. /* @param {HTMLElement} [options.target=document.body] - DOM element to which the script will be appended. /* @param {Object} [options.attributes={}] - Additional attributes to set on the script element. /* /* @returns {Promise} - Resolves when all scripts are loaded; rejects on any error. /*/ $core.import = function(scripts, { on, init, target = document.body, attributes = {} } = {}) { // Ensure scripts is always an array and not an already loaded script scripts = [].concat(scripts).filter(s => !(s?.src || document.querySelector(`script[src="${s}"]`))); typeof init === 'function' && init(scripts); return Promise.all(scripts.map(script => new Promise((resolve, reject) => { let el = script instanceof HTMLElement ? script : document.createElement('script'); if ( el.tagName === 'SCRIPT' ) { if ( el.querySelector(':is(script[data-src])') ) { el.src = el.getAttribute('data-src'); } else { Object.entries({ ...attributes, src: script }).forEach(([attr, val]) => el.setAttribute(attr, val)); } } else { return reject({ message: `Invalid input!`, data: script }); } el.addEventListener('load', resolve(el, script)); el.addEventListener('error', (e) => reject({ message: `Failed to load script: ${e.target.src}`, event: e })); // If an `on` function is provided, pass the script element to it for manual deferring typeof on === 'function' ? on(el, script) : target.appendChild(el); }))); }; /** /* Map global KV localStorage wrapper to $core for convenience. /*/ $core.kv = $kv; /** /* Proximity sensor helper method. Triggers a callback function when the mouse is within /* a certain distance of the given element. With the optional `check` flag set to `true` the method /* will check if the target element is reachable by the user, e.g. not hidden or obstructed by /* other elements. These checks are off by default, as they will be triggered with every tracked /* mousemove event which can potentially cause performance issues. If the target element is initially /* hidden, consider binding `$near()` AFTER the element has become visible! /* /* @usage /* ``` /* $near('.my-button', 50, (el, threshold) => { /* console.log(`Mouse is within ${threshold}px of ${el}`); /* }); /* ``` /* /* @param {Element|String} el - The target DOM element or a selector string to identify the element. /* @param {Number} threshold - The proximity threshold (in pixels) at which the callback will be invoked. /* @param {Function} fn - The callback function to be invoked when the mouse is within the defined distance of the target. /* @param {Boolean} [once=false] - If true, the callback will be triggered only once. /* @param {Boolean} [check=false] - If true, performs enhanced checks on the element that it's visible and not obstructed. /* /* @returns {void} /*/ $core.near = function(el, threshold, fn, { once = false, check = false } = {}) { el = typeof el === 'string' ? document.querySelector(el) : el; if ( !el || typeof fn !== 'function' || typeof threshold !== 'number' ) { return; } // make sure element exists let run = false; let within = false; // flag to track if the mouse is inside the proximity zone const proximity = function(target, x, y) { const { left, right, top, bottom } = target.getBoundingClientRect(); if ( check ) { const style = window.getComputedStyle(target); if ( Object.entries({ display: 'none', visibility: 'hidden', opacity: '0' }).some(([p, v]) => style[p] === v) ) { return false; } } return x > left - threshold && x < right + threshold && y > top - threshold && y < bottom + threshold; }; const handler = (e) => { if (run) { return; } run = true; window.requestAnimationFrame(() => { const near = proximity(el, e.clientX, e.clientY); if ( near && !within ) { within = true; fn(el, threshold); once && window.removeEventListener('mousemove', handler); } else if ( !near && within ) { within = false; } run = false; }); }; window.addEventListener('mousemove', handler); // return a function that removes the event listener when called return () => window.removeEventListener('mousemove', handler); }; /** /* This is the velocity parser implemented in core ported over to javascript, but it's only /* half useful, as it can't interpolate native strings, so this is only used to interpret /* non-interpolated velocity expressions. /*/ $core.parseVelocity = function(key = '', value = '', args = []) { // Cast all placeholder values to strings, otherwise the parser can't deal with the args args = args.map(String); const scopes_map = { component: $core.config.file, device: $core.config.user.device, page: $core.config.node.quilt, place: $core.config.node.type, lang: $core.config.user.lang, }; const scopes_matches = [...key.matchAll(/@([^@\r\n\t\f\v ]+)/gm)] || []; const scopes = scopes_matches.reduce((obj, match) => { const scope = match[1].split(':')[0]; const value = match[1].split(':')[1]; if ( scopes_map[scope] ) { obj[scope] = (scopes_map[scope] == value); } return obj; }, {}); const choice = (expr = '', args = []) => { const value = args[parseInt(expr.split(',').shift())]; const choices = expr.split('choice,').pop().trim(); return choices.match(`(${value})#(.*?)\\|`)?.[2] || choices.split('|').pop().replace(/\d+</, '') }; const rx = /\$\{(?<i>.+?)\}|\{(?<p>[0-5])\}|\{(?<e>[^\$]+?)\}/gm; let result = value; let cnt = 1; // Enter recursion to resolve nested velocity variables/placeholders/expressions // Have a safety max recursion depth to avoid unintentional memory leaks / endless loops while ( result.match(rx) ) { result = $core.parseVelocity(key, value.replace(rx, (match, i, p, e) => (i ? $core.str(i, null, ...args) : ( p ? args[parseInt(p)] || '' : choice(e, args) ))), args).value; if ( cnt >= 10 ) { console.warn('$core.parseVelocity(): Recursion depth of 10 exceeded!', result, result.match(rx), !!result.match(rx)); break; } cnt++; } return { key: key, value: result, scopes: scopes }; }; $core.serializeForm = function(form, json = false, filter, reducer) { // The shortest way of getting an object from FormData is // `Object.fromEntries((new FormData(form)).entries())` // but it will not handle select multiple inputs as the entries will have the same key // and therefore only the last selected option is returned as a value, to aggregate such array-like fields // properly, we have to manually loop over the fields after destructuring them into tuples (array of arrays) // this can also handle any type of field as an array of values by specifying the name with `[]` at the end // and furthermore it is also possible to directly aggregate form inputs into objects by specifying the name // attribute like so `name='object\{key}'`. NOTE: I'm not sure if the backslash escape is needed outside of // frameworks that interpolate expressions with `{`. Check the admin component for a practial example. const data = [...(new FormData(form)).entries()].reduce(reducer ? reducer : (obj, [name, value]) => { value = name.startsWith('(bool)') ? ({ '0': false, 'false': false, '1': true, 'true': true})[value.trim()] : value; const key = name.replace(/{(.+)}/gm, '').replace('(bool)', ''); value = obj[key] && key.endsWith('[]') ? [...obj[key], value] : ( key.endsWith('[]') ? [value] : ( /{(.+)}/gm.test(name) ? { ...obj[key], [[...name.matchAll(/{(?<k>.+)}/gm)][0]?.groups?.k]: value } : value ) ); if ( typeof filter === 'function' && filter(key, value) ) { obj[key] = value; } else { obj[key] = value; } return obj; }, {}); // Return a JSON string if requested return json ? JSON.stringify(data) : data; }; $core.obj = { /** /* Filters properties defined in props from the input object (non-destructive). /* /* @param {object} obj - The input object to operate on. /* @param {array} props - The properties to skip from the input object if present. /* /* @returns {object} - A new object without the properties defined in props. /*/ skip: function(obj = {}, props = []) { return Object.entries(obj).reduce((acc, [prop, value]) => { return props.includes(prop) ? acc : ((acc[prop] = value), acc); }, {}); }, /** /* Include only properties defined in props from the input object (non-destructive). /* Basically the opposite of _.skip(). /* /* @param {object} obj - The input object to operate on. /* @param {array} props - The properties to include from the input object if present. /* /* @returns {object} - A new object with only the properties defined in props. /*/ only: function(obj = {}, props = []) { return Object.entries(obj).reduce((acc, [prop, value]) => { return !props.includes(prop) ? acc : ((acc[prop] = value), acc); }, {}); }, }; $core.str = function(_key = null, _default = null, ...placeholders) { if ( !_key ) { return _default; } if ( _key && !_default ) { _default = _key; } // Try a direct lookup first, should work in most cases, except when there are @scopes // that weren't specified in the key let key = _key; let value = $core.properties[`${key}@component:${$core.config.file}`]; if ( !value ) { // Try to find a matching key by checking all properties //console.log(`$core.str(): could not find matching string for key ${key}: ${key}@component:${$core.config.file}, trying all properties:`, Object.entries($core.properties).find(([k, v]) => (k.includes(`@component:${$core.config.file}`) && k.split('@component').pop().includes(key)))); [ key, value ] = Object.entries($core.properties).find(([k, v]) => (k.includes(`@component:${$core.config.file}`) && k.split('@component').pop().includes(key))) || []; // console.log(`$core.str(): Could not find string by direct key (${_key}) lookup, find result:`, key, value); } /*else { console.log(`$core.str(): found matching string for key ${key}: ${key}@component:${$core.config.file}`); }*/ if ( value ) { return $core.parseVelocity(`${key}@component:${$core.config.file}`, value, placeholders).value; } else { return $core.parseVelocity(`${_key}@component:${$core.config.file}`, _default, placeholders).value; } }; /** /* Helper method facilitating watching for a DOM element for mutations and run a callback on them. /* /* @usage /* ``` /* $core.watch('<selector>', (mutation) => { /* console.log('I am here!', mutation); /* }, <optional:options>, <optional.immediate>); /* ``` /* /* @param {string} selector - The CSS selector to watch for mutations. /* @param {function} fn - The callback function to call when the element is mutated. /* @param {object} options - An optional options object for `.observe(<target>, <option>)` /* @param {boolean} immediate - If the callback function should be executed immediately once if the element is present. /*/ $core.watch = (selector, fn, options = {}, immediate = false) => { // Make sure nothing explodes in older browsers that do not support MutationObserver if ( window.MutationObserver ) { // Look for watched selector matching elements already present in the DOM, // and execute the callback on them immediately. if ( immediate && document.querySelector(selector) ) { [document.querySelector(selector)].forEach(fn); } options = { attributes: true, // can't have it set by default, otherwise not all attributes are monitored //attributeFilter: ['style', 'class'], attributeOldValue: false, characterData: false, characterDataOldValue: false, childList: false, subtree: false, ...(options || {}) } // One might have to do `const target = el.target as HTMLElement;` // within the callback to get an actual HTMLElement with its expected methods return (new MutationObserver(mutations => mutations.forEach(fn))) .observe(document.querySelector(selector), options); } }; /** /* Helper method facilitating waiting for a DOM element to appear and execute a callback. /* /* @usage `$core.when('<selector>', (el) => { console.log('I am here!', el); }, <optional:targetNode>);` /* /* @param {string} selector - The CSS selector to wait for. /* @param {function} fn - The callback function to call when the element appears. /* @param {HTMLElement} watch - The node to watch for mutations within. /*/ $core.when = (selector, fn, watch = document.body, existing = false) => { // Check if we even have a valid node to watch, otherwise MutationObserver will throw! watch = typeof watch === 'string' ? document.querySelector(watch) : watch; if ( !(watch instanceof Node) ) { console.warn(`$core.when(): 'watch' param wasn't a Node, aborting!`, watch); return; } // Make sure nothing explodes in older browsers that do not support MutationObserver if ( window.MutationObserver ) { // Look for watched selector matching elements already present in the DOM, // and execute the callback on them immediately. if ( document.querySelectorAll(selector).length ) { document.querySelectorAll(selector).forEach(fn); } return (new MutationObserver(mutations => [...mutations] .flatMap((mutation) => [...mutation.addedNodes]) .filter((node) => node.matches && node.matches(selector)) .forEach(fn))) .observe(watch, { childList: true, subtree: true }); } }; } // Initialize Tools // Make sure jQuery is available in some form which is not always the case in admin/studio and // several tools rely on it. if ( $ ) { // Tools only useful in admin/studio if ( $('body').is('.BizAppsPage') ) { Object.entries(tools.admin).map(function([name, fn]) { if ( typeof fn === 'function' ) { $core?.config?.user?.admin && console.info(`core.cmp.tools: Running tools.admin.${name}()`); fn(); } else { $core?.config?.user?.admin && console.warn(`core.cmp.tools: fn was not a function!`, fn); } }); } } // Bootstrap riot with global stuff if present // TODO: Maybe this should be its own component, separated from general purpose tools code! if ( window.riot ) { /** /* Stateless minimal router. /* Being stateless is a feature! The real "state" (i.e., the current route, history, etc.) /* is managed by the browser itself through the History API and the current URL. /* The router's job is to react to changes in that state and inform the rest of the app /* (via events or other mechanisms) about those changes. /*/ riot.$router = function(base = '/', options = {}) { const routes = []; base = '/' + base.replace(/^\/|\/$/g, ''); function parseRoute(route) { const { k, r } = route.split('/').reduce(({ k, r }, segment, i) => { switch (segment[0]) { case '*': return { k: k.concat('*'), r: r + '/(?<wild>.*)' }; case ':': const { key, con, ext, opt } = segment.match(/^:(?<key>[^\s(.?]+)(?:\((?<con>[\S]+?)\)(?![\)]))?(?<opt>\?)?\.?\(?(?<ext>[a-z0-9|]+)?\)?$/i)?.groups || {}; const p = `(?<${key}>${(con || '[^/]+?')}${ext ? `\\.(?<ext>${ext})` : ''})`; return key ? { k: k.concat(key), r: r + (opt ? `(?:/${p})?` : `/${p}`), } : { k, r }; default: console.log('parse index', i, Boolean(i)); return segment ? { k, r: r + `/(?<type${i > 1 ? i : ''}>${segment})` } : { k, r }; } }, { k: [], r: '' }); return { keys: k, pattern: new RegExp('^' + r + '/?$','i'), }; } function on(route, handler) { const { keys, pattern } = parseRoute(route); routes.push({ keys, pattern, handler }); } function navigate(path, replace) { const url = new URL(path, location.origin); const { keys, pattern } = parse(url.pathname); const match = routes.find(r => pattern.test(r.path)); if (match) { const match = match.pattern.exec(path); history[replace ? 'replaceState' : 'pushState'](null, null, path); $trigger('route', { url, match, keys, pattern }); } else if (options['404']) { options['404'](path); } } document.addEventListener('click', e => { const href = e.target.closest('a') && e.target.getAttribute('href'); const skip = [ !href, href.startsWith('#'), href.startsWith('javascript:'), !href.startsWith(base), e.defaultPrevented, e.button !== 0, e.metaKey, e.ctrlKey, e.shiftKey, e.altKey, ...(options.skipConditions || []) ]; if ( skip.some((con) => (typeof con === 'function' ? con(e) : Boolean(con))) ) { return; } e.preventDefault(); navigate(href); }); return { on, navigate }; }; riot.install((cmp) => { /** /* Override native riot `tag.$` (we don't touch `tag.$$`) with a much more powerful /* jQuery like API (it's not complete of course, but very mighty for 3.6KB code)! /* Of course we could also just map an already present jQuery instance to `tag.$`! /* This doesn't work, riot component internals are frozen! /*/ /*delete cmp.$; cmp['$'] = (selector) => { console.log('overwritten tag.$', selector); return cmp.$(selector); }; */ /** /* Install global event bus proxy methods. We do not want these methods scoped to every /* component individually but for them to be the same for all components so they can talk /* to each other on a global scope. /* /* - `on` will automatically bind `this` within the event listener function to the component. /*/ // This auto binding magic creates trouble, bind the component to the handler yourself if needed! //cmp['on'] = (e, fn, once) => $on(e, fn.bind(cmp), once); cmp['on'] = $on; cmp['off'] = $off; cmp['trigger'] = $trigger; /** /* Look for 'magic' listener methods defined within the component and automatically /* create a listener for them. These methods need to be named in a particular way: /* `$on<event.name.capitalized>` (note the $ prefix!), e.g. for an event type 'results' /* the component method needs to be defined as `$onResults: (e, results) => {}`, /* This approach is eliminating the need to define an explicit event listener somewhere /* within a regular lifecycle method with `tag.on('results', (e, results) => {})`... /* Using this automatic approach eliminates the need to remove any explicitely defined /* event listeners when a component is unmounted, as this is done automatically through /* the proxied lifecycle methods below. /* /* @note Be aware that if you need `this` bound to the component within your listeners /* (regardless if automatic or explicitely defined) you need to use the `function` way /* e.g. `$onResults: funciton(e, results) {}`! /* /* As this will create a listener for all events for every component, it's not enabled /* by default, only when the component has a property `events: true`. This way 'dumb' /* components do not get useless listeners registered which have to be processed! /*/ // We can either override riot.$trigger or 'properly' use the event bus, I'm not sure what // is actually better, both approaches seem to work just fine, when overriding obviously // there are no listeners stored and thus don't need to be removed when unmounting, resulting // in slightly less bootstrap code, but other than that it somehow feels wrong to me... /* if ( cmp.events ) { const $trigger = riot.$trigger; riot.$trigger = async (e, ...args) => { const $ = `$on${e.replace(/\b\w/, (c) => c.toUpperCase())}`; if ( cmp[$] ) { console.log(`automatically triggering ${$} listener for ${cmp.name}`); await cmp[$](e, ...args); } await $trigger(e, ...args); }; } */ let off = null; if ( cmp.events ) { // `on` will return a function to remove that listener, we store it for auto-cleanup off = cmp.on('*', async function(e, ...args) { const $ = `$on${e.replace(/\b\w/, (c) => c.toUpperCase())}`; if ( cmp[$] ) { await cmp[$](e, ...args); } }); } /** /* Proxy component lifecycle methods to give some debug info in debug mode /* allows us to define debugging stuff once here instead of having to repeat /* the same lengthy logging code in every single component increasing bundle /* size for nothing... we can reference component.name safely as it is part of the /* default component implementation along with .css and .template even though those /* properties are not enumerable (e.g. shown when console.log(component)) /* to proxy lifecycle methods simply set the .proxy property within the component to true /* if undefined or false nothing will be done here (e.g. the proxy is opt-in) /*/ ['onBeforeMount', 'onMounted', 'onBeforeUnmount'].forEach((method) => { // make a reference to the original lifecycle method and bind the component to it const org = cmp[method].bind(cmp); // add proxy method calling the original after it has done global stuff cmp[method] = (props, state) => { org(cmp.props, cmp.state); // automatically removes auto-event listeners when the component is unmounted if ( method === 'onBeforeUnmount' && off ) { off(); } cmp.debug && console.log(`${cmp.name}.${method}()`, cmp.props, cmp.state, method === 'onBeforeMount' ? cmp : null); }; }); //console.log('riot.install() done!', cmp); }); } if ( $core?.config?.env?.includes('stage') && $core?.config?.user?.roles?.includes('GlowingBlue') && document.querySelectorAll('[type*="riot"]').length && window.riot ) { // Live compiling riot components in dev mode console.log('core.cmp.tools: Compiling and registering riot components'); (async () => { await $core.compile(); // Useful for profiling component performance if ( window.performance ) { window.start = performance.now(); } if ( $trigger ) { await $trigger('compiled'); } })(); } else { // directly trigger on prod as components are pre-compiled! (async () => { if ( $trigger ) { // Wait for next event loop, if not it can cause strange non-mounting issues // due to no work having to be done (e.g. compile event basically happening immediately) // and the order in which @liaAddScript adds the JS from various places within the codebase await $wait(0); await $trigger('compiled'); } })(); } //document.addEventListener('DOMContentLoaded', function() {}); })(LITHIUM?.jQuery || jQuery); // Pull in global jQuery reference // </script> // <script> just for inline syntax-highlighting... ;(function($){ // This is very much work in progress, but a synk object should generally look something like: /* { type: <event-type-synk-understands>, event: { data: {}, source: { node: {}, page: {}, user: {}, }, verified: <bool>, } } */ // Store objectified request payloads for later use let payload = {}; // Define which particular routes we want to forward to synk. Those can be from forms, // links etc. // The key is the form action or link (partial) to check for when a request comes in // the value is an object with an `only`array for filtering the payload and a `type` // function to dynamically determine the type of event that is witnessed. // TODO: Look into those `t:cp=solutions/contributions/acceptedsolutionsactions` // identifiers that most actions seem to have, maybe they are an easier to detect way // of what to track on different pages, then the changing URL's which force us to use // URL partials... => unfortunately not, the identifier stays the same, the action is // still burried in the URL, like `markmessageasacceptedsolutionsecondarybutton` (WTF?): // t5/forums/v5/forumtopicpage.markmessageasacceptedsolutionsecondarybutton/message-uid/1844 const synk = { // AJAX: inline reply form submit 'inlinemessagereplyeditor.form.form.form.form': { only: [ 'attachment-key', 'liaFormContentKey', 'mediaSnippetUrl', 'multipleUpload', 'parentMessageRef', 't:ac', 'tags_', 'tinyMceEditor', ], type: (xhr) => 'post-reply', }, // AJAX: Kudos button 'kudosbuttonv2.kudoentity': { only: ['triggerEvent', 'parameterOverrides'], type: (xhr) => (xhr.responseURL.includes('revoke-kudos/true') ? 'dislike' : 'like'), }, // not AJAX: Report content to moderator 'notifymoderatorform.form.form.form': { type: (xhr) => 'mod-check', }, // subscribe: /t5/forums/v5/forumtopicpage.__addmessageuseremailsubscription__/message-uid/1841?t:cp=subscriptions/contributions/messageactions // post-mute: /t5/forums/v5/forumtopicpage.__addmessageusermute__/message-uid/1924?t:cp=subscriptions/contributions/messageactions // bookmark: /t5/forums/v5/forumtopicpage.__addmessageuserbookmark__/message-uid/1924?t:cp=subscriptions/contributions/messageactions // post-edit: /t5/forums/v5/forumtopicpage.__editmessageinline:editmessage__/message-uid/1924?t:cp=boards/contributions/messageactions // post-delete: /t5/forums/v5/forumtopicpage.__deletemessage:deletemessage__/message-uid/1924?t:cp=boards/contributions/messageactions // post-move: /t5/forums/v5/forumtopicpage.__movemessage:movemessage__/message-uid/1924?t:cp=boards/contributions/messageactions // post-solved: /t5/forums/v5/forumtopicpage.__markmessageasacceptedsolutionsecondarybutton__/message-uid/1844?t:cp=solutions/contributions/acceptedsolutionsactions // post-unsolved: /t5/forums/v5/forumtopicpage.__unmarkmessageasacceptedsolution__/message-uid/1844?t:cp=solutions/contributions/acceptedsolutionsactions }; $on('xhr', (e, state, xhr, ...args) => { //console.log('$onXHR', e, xhr, args); if ( state === 'open' ) { $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: Request created', args, xhr); } else if ( state === 'send' ) { $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: Request payload is about to be sent', Object.fromEntries([...(new URLSearchParams(args[0]))])); payload = args[0] ? Object.fromEntries([...(new URLSearchParams(args[0]))]) : {}; } else if ( state === 'response' ) { $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: Can modify response text!', args, xhr); // important to return modified response from here return args[0]; } else if ( state === 'done' ) { if ( !Object.entries(synk).some(([urlpartial, obj]) => xhr.responseURL.includes(urlpartial)) ) { $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: skipping response processing for', xhr.responseURL); return; } $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: Response received', xhr.responseText); try { // Do some basic sanity checks before attempting to parse JSON... // Usually Khoros XHR responses are a huge object that is then processed and // injected into the current page via some very convoluted logic, most of the data // is irrelevant for us and is contained within a response's `components` property let data = xhr.responseText.trim().startsWith('{') ? $core.obj.skip(JSON.parse(xhr.responseText)?.response, ['components']) : xhr.responseText; if ( typeof data !== 'string' ) { const { only, type } = (Object.entries(synk).find(([urlpartial, obj]) => xhr.responseURL.includes(urlpartial))[1] || {}); data = { type: type(xhr) || 'undefined', event: { url: xhr.responseURL, timestamp: (new Date()).toISOString(), // the result of the event data: data, // the initiator of the event source: { payload: $core.obj.only(payload, (only || [])), user: $core.obj.skip(LITHIUM.CommunityJsonObject.User, ['settings', 'policies', 'emailRef']), node: LITHIUM.CommunityJsonObject.CoreNode, page: { ...($core.obj.only(LITHIUM.CommunityJsonObject.Page, ['object'])?.object || {}), }, } } }; $trigger('synk', data); //console.log('Forwarded data:', data); } else { $core.config.devmode === 'xhr' && console.warn('XHRmiddlaware: Respsone was a string, skipping sync!'); } } catch(ex) { console.error(ex); } } }); // The above handles (old school) AJAX requests, but we also need to deal with actions/events // that occur the even old-schooler way through regular link clicks that reload the page. // To do that we attach a global link listener. I believe to catch Khoros related events we // can safely filter links by `data-lia-action-token` as all the relevant action links seem // to have such an attribute! // TODO: Make sure we do not somehow also track links that are handled by AJAX requests and // thus would be double-synk'ed... document.querySelectorAll('a[data-lia-action-token]').forEach((el) => { el.addEventListener('click', (e) => { if ( e.target.getAttribute('href') ) { const url = new URL(e.target.getAttribute('href')); const params = Object.fromEntries([...url.searchParams]); $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: Action link params', url, params, e.target.getAttribute('href')); } else { $core.config.devmode === 'xhr' && console.warn('XHRmiddlaware: Action link did not have a href attribute?', e.target, e); } if ( $core.config.user.roles.includes('GlowingBlue') ) { $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: Preventing action from glowingblue user'); //e.preventDefault(); } // We would then build a synk object and trigger a 'synk' event through the global // event bus... // TODO: We also need to think about what happens if an action fails, maybe storing // potential sync objects in localStorage with an attribute of `verified: false` // would be a good idea, for AJAX requests we can more or less reliably track // if an action was successful, as the returned objects contain a property `state` // (NOT `status`, that one is always 'success'!) that will indicate any errors... }); }); // There are also forms that are not handled via AJAX! It seems this listener does not // conflict with the AJAX ones, as those are implemented by Khoros earlier on, so this // listener is never called for AJAX handled forms, which is good because then we don't // have to deal with duplicate synk events... document.querySelectorAll('input[name="lia-action-token"]').forEach((el) => { $(el).parents('form')[0].addEventListener('submit', (e) => { if ( $core.config.user.roles.includes('GlowingBlue') ) { $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: Preventing form submission from glowingblue user'); //e.preventDefault(); $core.config.devmode === 'xhr' && console.log('XHRmiddlaware: Serialized form', $core.serializeForm(e.target)); } else { //return true; } }); }); $on('synk', (e, data) => { console.log('$onSynk', e, data); // TODO: make sure there is no issue with aborted requests due to page reloading // if it is, we might have to store the synk objects in local storage before sending // out the request and then when we get a successful response, delete them and on page // load check if we have any leftover items to synk and process those again... // TODO: Push requests through proxy (not sure if it already handles POST requests!) // So we do not leak any user IP information to a third party which Deno deploy is... fetch('https://synk.deno.dev', {method:'POST', body: JSON.stringify(data)}); }); // Simply trigger a custom xhr event and handle whatever logic in the event listener! XMLHttpRequest.$use((xhr, state, ...args) => { $trigger('xhr', state, xhr, ...args); }); })(LITHIUM.jQuery); // Pull in global jQuery reference // </script> // <script> just for inline syntax-highlighting... ;(function($){ /** /* Deals with any redirects that need to be handled in JavaScript for some reason, like 404 Pages /* as we don't get the request in page.init and therefore can't do a redirect form there. /* /* You only need to specify the project specific redirects by adding them to the global `$core` /* object. The actual redirect logic will be dealt with from `core.cmp.tools` via `tools.redirect()`, /* just make sure you don't change the property name as the tool method looks for `$core.redirects`. /* /* @usage: Just define key/value paris where key = url.pathname and value the url to redirect to /*/ $core.redirects = { //'/some/path/to/redirect': 'https://www.community.tld/some/url/to/target/redirect/to', }; /** /* @issue KBCOM-2655 /* /* Immediately makes the inline 'Reply' button disabled on click instead of waiting for TinyMCE /* to be initialized as OOB does. /*/ $('.lia-component-messages-widget-reply-inline-button .lia-button.lia-action-reply').each(function() { $(this).on('click', function(e) { $(this).attr('disabled', true) }); }); })(LITHIUM.jQuery); // Pull in global jQuery reference // </script> // <script> ;(function() { if ( $core ) { $core.config.file = 'cmp.global.search-external'; $core.properties = { ...($core.properties || {}), ...{"general.in@component:cmp.global.search-external" : "in","general.of@component:cmp.global.search-external" : "of","general.from-community@component:cmp.global.search-external" : "From the Community","title@component:cmp.global.search-external" : "Additional Resources","filter.title.resources@component:cmp.global.search-external" : "Included Resources","filter.title.languages@component:cmp.global.search-external" : "Languages","filter.knowledge@component:cmp.global.search-external" : "Knowledge Base","filter.academy@component:cmp.global.search-external" : "Academy","filter.cms@component:cmp.global.search-external" : "CMS Documentation","filter.api@component:cmp.global.search-external" : "API Documentation","filter.customer@component:cmp.global.search-external" : "Customer Blog","filter.language.en@component:cmp.global.search-external" : "en","filter.language.de@component:cmp.global.search-external" : "de","filter.language.es@component:cmp.global.search-external" : "es","filter.language.fr@component:cmp.global.search-external" : "fr","filter.language.pt@component:cmp.global.search-external" : "pt","filter.language.ja@component:cmp.global.search-external" : "ja","result@component:cmp.global.search-external" : "result","results@component:cmp.global.search-external" : "results","results.in@component:cmp.global.search-external" : "{0} {0,choice,0#${results}|1#${result}|1<${results}} ${general.in} {1}","results.none.title@component:cmp.global.search-external" : "No results for \"{0}\"","results.none.text@component:cmp.global.search-external" : "Try a different search term or use the filter on the left to search other resources.","paging.prev@component:cmp.global.search-external" : "Prev","paging.next@component:cmp.global.search-external" : "Next"}, }; } })(); // </script> // <script> just for inline syntax-highlighting... ;(function($){ const params = (new URL(window.location.href)).searchParams; // Allows testing production behavior on stage by switching env temporarily via URL param if ( $core.config.env.includes('stage') && params.get('test') ) { $core.config.env = 'prod'; console.warn('Production test mode enabled', $core.config); } // The most primitive in-memory cache you can imagine // it will hold HubSpot search API results as long as the page is not reloaded, this // speeds things up drastically when using typeahead (e.g. reacting to every key stroke) $core.cache = new Map(); const title = $core.str('title', 'Additional Resources'); /** /* Searchbar autosuggest integration. /* We can't really use riot here as we are hacking into the existing Khoros auto-suggest dropdown /* Furthermore the SearchForm is added twice to every page due to how the mobile header was done. /* It's entirely separate from the desktop header and therefore needs to be targetet properly when /* it's visible from 1024px down. /*/ const search_form = window.innerWidth <= 1024 ? '.mobile-header form.SearchForm' : '.community-header-nav .SearchForm'; $core.when(`${search_form} [name="messageSearchField"] + .lia-autocomplete-container .lia-autocomplete-content`, function(el) { $(`${search_form} [name="messageSearchField"]`).each(function() { var $input = $(this); var $results = $input.find('+ .lia-autocomplete-container .lia-autocomplete-content'); /** /* The autosuggest-dropdown closes on a click anywhere besides it's own results, /* not too crazy of an issue, but it annoys me and I can't fix it, don't know what /* triggers which event, tried to find out but without success... /*/ /*if ( window.innerWidth <= 1024 ) { $results .prepend(`<i class="collapse-results lia-autocomplete-no-event-item lia-fa fa-chevron-up p:x15 p:y11 pos:a pos:r0 pos:t0"></i>`) .find('.collapse-results') .on('click', function(e) { e.stopPropagation(); console.log($(this).siblings('ul:first'), $(this)); $(this).toggleClass('fa-chevron-up fa-chevron-down'); }); }*/ if ( !$results.find('ul.custom-external-results').length ) { $results.append(`<ul class="custom-external-results d:f(row/0/1/100%) h:max300 d:scroll(y) t:/12//400 d:b:before p:y8:before p:l15:before pos:r t:ucase:before" aria-label="${title}" data-before="${title}"></ul>`); } var $container = $results.find('ul.custom-external-results'); $input.on('input', async function(e) { // TODO: find out what is the URL param to limit `limit=` does not work... // => well, it seems the autocomplete endpoint of the HubSpot search API does not // support any kind of configuration at all. If it's needed, we might have to switch // to the slower 'full' API endpoint var url = `https://wtcfns.hubspot.com/wt-api/search/autocomplete?queryString=${$input.val()}&language=${$core.config.user.lang}&limit=5`; //console.log($input.val()); if ( !$core.cache.has(url) ) { $core.cache.set(url, (await (await $core.fetch(url)).json())); } //console.log($core.cache.get(url)); // HubSpot search API will return `{'message': 'Missing search key'}` when the `queryString` is empty // in that case, markup will be `undefined`, which we have to catch, otherwise it's going to be // an empty array if there are really no results for a search term. var markup = $core.cache.get(url).data?.searchResults?.results?.reduce((r, v) => { // As limit doesn't work on the autocomplete endpoint we have to limit the auto-suggest results like this //if ( r.length < 3) { r.push(` <li class="lia-autocomplete-node-item lia-autocomplete-custom-item"> <a class="lia-link-navigation board-icon" tabindex="-1" href="#"> <span class="custom-img-icon-help lia-fa-icon lia-fa-question lia-fa bg:--color-lorax t:--color-olaf" title="${v.resource}" aria-label="${v.resource}" role="img"></span> </a> <a class="lia-link-navigation lia-js-autocomplete-list-item-link lia-autocomplete-message-list-item-link" tabindex="-1" href="${v.url}" target="_blank"> ${v.title.replace('hs-search-highlight hs-highlight-title', 'lia-search-match-lithium')} </a> <div class="lia-autocomplete-suggestion-additional-details lia-component-nodes-widget-auto-complete-node-list-item"> <span class="lia-autocomplete-suggestion-board-title t:caps">${v.resource}</span> </div> </li> `); //} return r; }, []); if ( markup === []._ ) { return; } if ( markup.length ) { $container.html(markup.join('\n')); } else { $container.html(`<div class="pos:center t:center t:/14">${$core.str('results.none.title', null, $input.val())}</div>`); } }); }); }, document.querySelector(`${search_form}`)); /** /* Global SearchPage integration of external search. /* Adds a new tab to the search sections area and additionally a filter-like fake dropdown /* That additional filter was removed again via KBCOM-2830! /* that triggers a click on the new tab, it's just for more visual exposure as we worry the /* new tab might be too unassuming and might be overlooked. /*/ if ( $core.config.node.quilt.includes('SearchPage') ) { let results = null; const getResults = async function(query, resources = ['knowledge', 'academy', /*'customer', 'api', 'cms'*/], language = ($core?.config?.user?.lang || 'en'), page = 1, limit = 10, offset = 0, padding = 2) { offset = (page-1) * limit; // HubSpot search API resources are targeted with `contentKey: api, cms, knowledge, academy, customer` const url = `https://wtcfns.hubspot.com/wt-api/search?queryString=${query}&limit=${limit}&offset=${offset}&page=${page}&language=${language}&contentKey=${resources.join(',')}`; //console.log(url); if ( !$core.cache.has(url) ) { $core.cache.set(url, (await (await $core.fetch(url, null, 'appcache')).json())); } // once received, the HubSpot search API results are agumented with custom stuff // that helps rendering things like pagination etc. let results = Object.entries(($core.cache.get(url)?.data?.searchResults || {})).reduce((r, [k, v]) => ({ ...r, [k]: v }), { active: resources, lang: language, query: query, url: url }); // calculate the total amount of pages first and set it to minimum 1 const pages = Array.from({length: Math.max(Math.ceil(results.total/limit), 1)}, (el, i) => i+1); // calculate collection object const collection = { limit: limit, offset: offset, total: results.total, paging: { page: page, pages: pages.length, // TODO: there are still some issues with this, it does work for page // 1, but for the last page, only 3 (instead of 5) pages are returned display: pages.length ? (() => { const num = (padding * 2) + 1; const i = pages.indexOf(page); const from = Math.max(i - Math.floor(num / 2), 0); const to = Math.min(from + num - 1, pages.length - 1); return pages.slice(from, to + 1); })() : [], // number of page-links left and right of current page padding: padding, // rendering helpers is_first: page === 1, is_last: page == pages.length } }; results = { ...results, collection: collection }; //console.log('response', res); //console.log('results', results); // Trigger a custom jQuery event globally that we can hook into from any other code $(document).trigger('results', results); // We can trigger the custom global riot event-bus from outside of components as it // is attached to the global riot object! if ( $trigger ) { $trigger('results', results); } return results; }; const injectSearchExternal = function() { // Inject our custom tab const $tab = $('.lia-search-tab-bar .lia-component-search-tabs .lia-tabs-standard').append(` <li role="presentation" class="search-external-tab lia-tabs lia-tabs-inactive is--custom"> <span><a class="search-external-link lia-link-navigation tab-link" role="tab" aria-selected="false" tabindex="0" href="${window.location.href}">${title}</a></span> </li> `).find('.search-external-tab'); // Removed via KBCOM-2830 /* const $filter = $(` <div class="lia-form-fieldset-wrapper lia-component-search-widget-external is--custom"> <a href="${window.location.href}" class="lia-common-dropdown-toggle" role="button">${title}</a> </div> `).insertBefore('.lia-component-quilt-search-page-thread-filters .lia-component-search-widget-location-filter'); */ // Handle clicks on our new tab: // The idea is to basically wipe the existing content from the page and mount the // custom search component instead. Any click on the regular tabs will behave like always and // trigger a page reload which will show the original content again $tab.find('.search-external-link').on('click', function(e) { e.preventDefault(); // Handle active state of tabs $(this).parents('.search-external-tab').addClass('lia-tabs-active').siblings('.lia-tabs-active').removeClass('lia-tabs-active'); // Then we clean out some of the content of the current page and make it look like a tab switch $('.lia-search-tab-bar .lia-component-search-widget-advanced-search-toggle, .lia-search-tab-bar .lia-component-search-actions, .lia-search-results .search-result-sorting').remove(); $('.lia-search-results .search-result-count').remove(); // Once cleaned up we inject the base tag and mount our custom component $('.lia-quilt-column-main-content .lia-quilt-row-main').empty().append('<div is="search-external" class="p:x15" data-cmp="cmp.global.search-external"></div>'); riot.mount('[is]:not([is] [is])', { title: title, results: results, params: params, getResults: getResults }); }); // The fake injected filter simply triggers the tab, it's meant to provide greater exposure (visually) // $filter.on('click', function(e) { // e.preventDefault(); // $tab.find('.search-external-link').trigger('click'); // }); }; // Pre-fetch results, why wait as we already know the query here, this also allows to inject a variety of // dynamic information into the native search results content and agument it with external results data // this also deals with the fact that the search page is an Angular component that reloads dynamically // when doing certain things... (not tab switching though) $core.when('.lia-message-search-container', async (el) => { if ( !document.querySelector('.lia-tabs.external-tab') ) { //console.log('Injecting external search!'); injectSearchExternal(); results = await getResults(params.get('q')); } }); // Attach custom event listener here to deal with non-component DOM updates as I don't want to handle those // within the custom component but also be updated if something changes there... $(document).on('results', function(e, results) { //console.log('onResults', e, results); if ( $('.lia-search-results .search-result-count').text().trim().length ) { $('.lia-search-results .search-result-count').attr('data-after', $core.str('general.from-community')); } // Update the tab tag $('.search-external-link').attr('data-after', (results?.total || 0)); }); } // Re-set production test mode if ( $core.config.env.match('stage') && params.get('test') ) { $core.config.env = 'stage'; } })(LITHIUM.jQuery); // Pull in global jQuery reference // </script> (function($) { //START END-USER CONFIGURATION //------------------------------ //selectors for hover card triggers var allHoverCardTriggers = '.author-name-link,.friend-list .friend a,.username a,.avatar,.user-avatar,.author-img, .authors a, .messageauthorusername a, a.lia-user-name-link, .js-latest-post-by-from a, .user-online-list li a, a.UserAvatar, .customUsersOnline a, #authors a,.dashboard-followers a.user-name, .dashboard-following a.user-name,.author-login-wrapper a, .hb-leaderboard a, .author-img-floated'; // Forward calling page's URL params to endpoint URL as well, helps with testing! var params = (new URL(location.href)).searchParams; var userApiUrl = '/plugins/custom/hubspot/hubspot/hovercardendpoint?' + ((params.set('user_id', '') == []._) && params.toString()); if($('.hover-card-container').length<1){ $('body').append('<div class="hover-card-container"></div>'); } var cardWrapper = $('.hover-card-container'); var error = false; var thisUserID = ''; var thisUserLogin = ''; var userLink =''; var cardTimer; var leaveTimer; function mouseenter(Elem) { var thisEl = Elem; cardTimer = setTimeout(function(){ var docWidth = $(document).width(); var rightSide = false; var userLink = thisEl.attr('href'); if($('.ViewProfilePage').length && $('img.lia-user-avatar-profile',thisEl).length){thisUserID = '';} else if(thisEl.attr('href')=='#' || thisEl.attr('href')=='' || !userLink.match('viewprofilepage')){ return false;} else{ var thisLen = (userLink).split('/'); thisUserID = (thisLen)[thisLen.length-1]; } var thisCard = $('.profileCard[data-user='+thisUserID+']',cardWrapper); var cardId = 'userProfileCard-'+ thisUserID; var addAttr = thisEl.attr('aria-describedby',cardId); var thisElTopOffset = Math.round(thisEl.offset().top+(thisEl.height()/2)+30); var thisElbottomoffset = "auto"; var className = ""; var winHeight = $(window).height(); var elOffset = thisEl.offset(); var scrollTop = $(window).scrollTop(); var elementOffset = thisEl.offset().top; var distanceTop = (elementOffset - scrollTop); var distanceBottom = (winHeight + scrollTop) - (elOffset.top + thisEl.outerHeight(true)); var distanceLeft = Math.round(thisEl.offset().left); var bodyHight = $('body').height(); var topParam = ''; var bottomparam = ''; var position = ''; var className = 'topArrow'; cardId if(distanceBottom < 300 ){ if(distanceLeft < 59){ thisCard.removeClass('bottomArrow'); var className = 'leftArrow'; var distanceLeft = (distanceLeft)+(39); var thisElTopOffset = (thisElTopOffset)-(150); }else{ var thisElTopOffset = (thisElTopOffset)-(301); var className = 'bottomArrow'; thisCard.removeClass('topArrow'); thisCard.removeClass('leftArrow'); var distanceLeft = (distanceLeft)-(45); } } else{ if(distanceLeft < 59){ thisCard.removeClass('topArrow'); var className = 'leftArrow'; var distanceLeft = (distanceLeft)+(39); var thisElTopOffset = (thisElTopOffset)-(150); }else{ thisCard.removeClass('leftArrow'); thisCard.removeClass('bottomArrow').addClass('topArrow'); var distanceLeft = (distanceLeft)-(45); } } if(thisCard.length && $('.profileCard[data-user='+thisUserID+'] .preloader',cardWrapper).length<1){ $('.profileCard',cardWrapper).hide(); thisCard.addClass(className); rightSide?thisCard.addClass('rightArrow'):thisCard.removeClass('rightArrow'); thisCard.delay(0).css({'top':(thisElTopOffset),'left':distanceLeft,'bottom':thisElbottomoffset}).fadeIn(); } else { var ajaxReturn = ''; //just in case thisCard.remove(); //hover card wrapper markup var rightArrowClass = rightSide?'rightArrow':''; if(thisElTopOffset != "auto"){ topParam = 'px'; } if(thisElbottomoffset != "auto"){ bottomparam = 'px'; } var profileCardHtml = '<div id="'+cardId+'" role="tooltip" class="AllCard profileCard '+rightArrowClass+' '+className+'"style="display:block;top:'+thisElTopOffset+topParam+';left:'+distanceLeft+'px;bottom:'+thisElbottomoffset+bottomparam+';" data-user="'+thisUserID+'"></div>'; $.when( //get the background $.ajax({ type: 'GET', url: userApiUrl+thisUserID, dataType: 'html', success: function(data) { $('.profileCard',cardWrapper).hide(); ajaxReturn = data; } }) ) .done(function(){ cardWrapper.append(profileCardHtml); $('.profileCard[data-user='+thisUserID+']',cardWrapper).eq(0).empty().html(ajaxReturn); if($('.profileCard[data-user='+thisUserID+'] .preloader',cardWrapper).length){ $('.profileCard[data-user='+thisUserID+'] .preloader',cardWrapper).parents('div.profileCard').remove(); } }) .fail(function(){ //uh oh - bail out! $('.profileCard',cardWrapper).hide(); }); } }, 360); } function mouseleave(e) { clearTimeout(cardTimer); // glowingblue: When the user leaves the hovercard trigger, wait because the leaving could be // to interact with the hovercard, if we don't wait it will just disappear...because // we left the trigger, right...so we'll have another handler that check if the mouse is // over the hovercard and if so clears this timer, so the card doesn't close here leaveTimer = setTimeout(function() { if ($('.profileCard[data-user="'+thisUserID+'"]',cardWrapper).length) { $('.profileCard[data-user="'+thisUserID+'"]',cardWrapper).fadeOut('fast'); } else { $(".profileCard").fadeOut('fast'); } }, 2400); } $(document).on("mouseenter focusin", allHoverCardTriggers, function(event) { if(!($(this).parents().hasClass('custom-header'))&& !($(this).parents().hasClass('green-wrap'))){ (leaveTimer !== []._) && clearTimeout(leaveTimer); mouseenter($(this)); event.stopPropagation(); } }); $(document).on("mouseleave focusout", allHoverCardTriggers, function(event) { (leaveTimer !== []._) && clearTimeout(leaveTimer); mouseleave(event); event.stopPropagation(); }); // glowingblue: Add handlers for when the users interacts with the hovercard, no closing! $('.hover-card-container').on('mouseenter', function(e) { (leaveTimer !== []._) && clearTimeout(leaveTimer); }); $('.hover-card-container').on('mouseleave', function(e) { (leaveTimer !== []._) && clearTimeout(leaveTimer); if ( $(e.target).is('.profileCard[style*="block"]') ) { leaveTimer = setTimeout(function() { $(e.target).fadeOut('fast'); }, 2400); } }); // glowingblue: add one global root level click handler to also close any visible hovercards // if the user taps/clicks outside the hovercard $(document).on('mousedown', function(e) { if ( !$(e.target).parents('.hover-card-container').length ) { (leaveTimer != []._) && clearTimeout(leaveTimer); $('.hover-card-container .profileCard[style*="block"]').each(function() { $(this).fadeOut('fast'); }); } }); })(LITHIUM.jQuery); (function($) { <!-- Expire all cookies --> document.cookie = "advocacyToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; document.cookie = "Crowdvocate_jwt_token=; domain=.hubspot.com; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; document.cookie = "Crowdvocate_user_ck=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; })(LITHIUM.jQuery); (function($) { document.addEventListener('gdpr.allow', function() { if (document.querySelector('.lia-cookie-banner-alert-accept a')) { document.querySelector('.lia-cookie-banner-alert-accept a').click(); } }); })(LITHIUM.jQuery); LITHIUM.Link({"linkSelector":"a.lia-link-ticket-post-action"}); ;(function($){ var langMap = { 'en':'hubspot_community_en', 'es':'hubspot_community_es', 'fr':'hubspot_community_fr', 'ja':'hubspot_community_jp', 'pt-br':'hubspot_community_pt', 'de':'hubspot_community_de' } var nodeType = "board"; var langScope = langMap['en']; var isSearchPage = jQuery('body').hasClass('SearchPage'); var isIdeasLandingPage = jQuery('body').hasClass('ideaslandingpage'); if (nodeType === "community" && !isSearchPage && !isIdeasLandingPage) { var inputFormFilter = '<input name="filter" value="location" type="hidden">'; var inputFormLocation = '<input name="location" value="category:' + langScope + '" type="hidden">'; $('form.SearchForm').append(inputFormFilter).append(inputFormLocation); } else if (nodeType === "community" && isIdeasLandingPage) { var searchUrl = "/t5/forums/searchpage/tab/message?filter=location&location=idea-board:HubSpot_Ideas&collapse_discussion=true"; var query = jQuery('.SearchForm .lia-search-input-message').val(); jQuery(document).on('submit', 'form.SearchForm', function(e) { e.preventDefault(); var newQ = "&q=" + document.querySelector('.SearchForm .lia-search-input-wrapper input.search-input').value; window.location = window.location.origin + searchUrl + newQ; }) } })(LITHIUM.jQuery) LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c9763bdeac","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c9763bdeac_0","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c9763bdeac_1","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c9763bdeac_2","feedbackSelector":".InfoMessage"}); LITHIUM.AjaxFeedback(".lia-inline-ajax-feedback", "LITHIUM:hideAjaxFeedback", ".lia-inline-ajax-feedback-persist"); LITHIUM.Placeholder(); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.defaultAjaxFeedbackHtml = \"<div class=\\\"lia-inline-ajax-feedback lia-component-common-widget-ajax-feedback\\\">\\n\\t\\t\\t<div class=\\\"AjaxFeedback\\\" id=\\\"ajaxFeedback_2349c976625c50\\\"><\\/div>\\n\\t\\t\\t\\n\\t\\n\\n\\t\\n\\n\\t\\t<\\/div>\";LITHIUM.AjaxSupport.defaultAjaxErrorHtml = \"<span id=\\\"feedback-errorfeedback_2349c9766b88ea\\\"> <\\/span>\\n\\n\\t\\n\\t\\t<div class=\\\"InfoMessage lia-panel-feedback-inline-alert lia-component-common-widget-feedback\\\" id=\\\"feedback_2349c9766b88ea\\\">\\n\\t\\t\\t<div role=\\\"alert\\\" class=\\\"lia-text\\\">\\n\\t\\t\\t\\t\\n\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\t<p ng-non-bindable=\\\"\\\" tabindex=\\\"0\\\">\\n\\t\\t\\t\\t\\t\\tSorry, unable to complete the action you requested.\\n\\t\\t\\t\\t\\t<\\/p>\\n\\t\\t\\t\\t\\n\\n\\t\\t\\t\\t\\n\\n\\t\\t\\t\\t\\n\\n\\t\\t\\t\\t\\n\\t\\t\\t<\\/div>\\n\\n\\t\\t\\t\\n\\t\\t<\\/div>\";LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c9765a3579', 'disableAutoComplete', '#ajaxfeedback_2349c9763bdeac_0', 'LITHIUM:ajaxError', {}, 'lUoOrgmtV3Z-rtuj2dbmbV4jNVGrpCqA9X_l0Yktdq8.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"bAzlwotpXZKFxImOHpoT0VNsiM0HQCcWhLN2euDd1EU.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c9765a3579\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":3},"inputSelector":"#messageSearchField_2349c9763bdeac_0","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.messagesearchfield.messagesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c976823a9b', 'disableAutoComplete', '#ajaxfeedback_2349c9763bdeac_0', 'LITHIUM:ajaxError', {}, 'On6IDiin6pdCn9R3jG1VV9kEZiVJOIOc5xjoenvcotw.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"jUO-HdcCxL97GgrPkd1qavRiewhXPOstkZsoY12FSl4.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c976823a9b\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":3},"inputSelector":"#messageSearchField_2349c9763bdeac_1","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.tkbmessagesearchfield.messagesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching for users...","emptyText":"No Matches","successText":"Users found:","defaultText":"Enter a user name or rank","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c9769087a8', 'disableAutoComplete', '#ajaxfeedback_2349c9763bdeac_0', 'LITHIUM:ajaxError', {}, 'SdZB_RsPg4AwWES13TeFjJBJ5iYWaUBbt71Nujbcc-o.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"4nPNjABu2kHFlRX4AyC7jhgBKCco1q2SCEYMnj63PrM.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c9769087a8\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#userSearchField_2349c9763bdeac","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.usersearchfield.usersearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AjaxSupport({"ajaxOptionsParam":{"event":"LITHIUM:userExistsQuery","parameters":{"javascript.ignore_combine_and_minify":"true"}},"tokenId":"ajax","elementSelector":"#userSearchField_2349c9763bdeac","action":"userExistsQuery","feedbackSelector":"#ajaxfeedback_2349c9763bdeac_0","url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.usersearchfield:userexistsquery?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","ajaxErrorEventName":"LITHIUM:ajaxError","token":"BWt-S_xeN2Rqc265TKUDYISinmiG0mFpNkjsy9EPnbc."}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c976a0a798', 'disableAutoComplete', '#ajaxfeedback_2349c9763bdeac_0', 'LITHIUM:ajaxError', {}, 'qZ51tcLJbdU8olvF0yJGUwrCi6rASZnfbOO9oXeRtd4.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"3TqJCE7i_icqtIkGsC0TcQT-gI4pLQa57tTgjAfjAHI.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c976a0a798\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#noteSearchField_2349c9763bdeac_0","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.notesearchfield.notesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c976af80ec', 'disableAutoComplete', '#ajaxfeedback_2349c9763bdeac_0', 'LITHIUM:ajaxError', {}, 'GkPUCnwps1ZqCJh67RD0nMX2xHA0CNucyIL_J8uEdBU.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"aXFx61Q_d_ppE5kbIDDmTO2EO_jwsHvyxX9s41FPZKc.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c976af80ec\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#productSearchField_2349c9763bdeac","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.productsearchfield.productsearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AjaxSupport.fromLink('#enableAutoComplete_2349c9763bdeac', 'enableAutoComplete', '#ajaxfeedback_2349c9763bdeac_0', 'LITHIUM:ajaxError', {}, 'HITZp0m6VQ-T1qIiDuITwu8OoUa35CTODUkEjOWTIFU.', 'ajax'); LITHIUM.Tooltip({"bodySelector":"body#lia-body","delay":30,"enableOnClickForTrigger":false,"predelay":10,"triggerSelector":"#link_2349c9763bdeac","tooltipContentSelector":"#link_2349c9763bdeac_0-tooltip-element .content","position":["bottom","left"],"tooltipElementSelector":"#link_2349c9763bdeac_0-tooltip-element","events":{"def":"focus mouseover keydown,blur mouseout keydown"},"hideOnLeave":true}); LITHIUM.HelpIcon({"selectors":{"helpIconSelector":".help-icon .lia-img-icon-help"}}); LITHIUM.SearchAutoCompleteToggle({"containerSelector":"#searchautocompletetoggle_2349c9763bdeac","enableAutoCompleteSelector":".search-autocomplete-toggle-link","enableAutocompleteSuccessEvent":"LITHIUM:ajaxSuccess:enableAutoComplete","disableAutoCompleteSelector":".lia-autocomplete-toggle-off","disableAutocompleteSuccessEvent":"LITHIUM:ajaxSuccess:disableAutoComplete","autoCompleteSelector":".lia-autocomplete-input"}); LITHIUM.SearchForm({"asSearchActionIdSelector":".lia-as-search-action-id","useAutoComplete":true,"selectSelector":".lia-search-form-granularity","useClearSearchButton":false,"buttonSelector":".lia-button-searchForm-action","asSearchActionIdParamName":"as-search-action-id","formSelector":"#lia-searchformV32_2349c9763bdeac","asSearchActionIdHeaderKey":"X-LI-AS-Search-Action-Id","inputSelector":"#messageSearchField_2349c9763bdeac_0:not(.lia-js-hidden)","clearSearchButtonSelector":null}); LITHIUM.Form.resetFieldForFocusFound(); (function($) { document.querySelector('a.login-link').classList.add('homepage-nav-login'); })(LITHIUM.jQuery); ;(function($){ $(document).ready(function() { $(".custom-user-menu-v2 .nav-link").click(function(e) { e.preventDefault(); $(".nav-popover.profile").toggleClass('show'); }); $(".search-toggle-action-icon-plus").on("click",function(e){ e.preventDefault(); $(this).parent().find(".plus-bar-main-content").toggle(); }); //User Avatar $('.header-tab-nav li span').click(function() { $('.header-tab-nav li span').removeClass("active"); if(this.id == 'profile'){ $('span#profile').addClass("active"); $('.header-tab-nav-content > div#profile-list-wrapper').show(); $('.header-tab-nav-content > div#admin-list-wrapper').hide(); $('.header-tab-nav-content > div#profile-list-wrapper').removeClass('profile-menu-dropdown'); } if(this.id == 'admin'){ $('span#admin').addClass("active"); $('.header-tab-nav-content > div#profile-list-wrapper').hide(); $('.header-tab-nav-content > div#admin-list-wrapper').show(); $('.header-tab-nav-content > div#profile-list-wrapper').addClass('profile-menu-dropdown'); } var indexer = $(this).index(); //gets the current index of (this) which is #header-tab-nav li $('.header-tab-nav-content > div:eq(' + indexer + ')').fadeIn(); //uses whatever index the link has to open the corresponding box }); $(this).mouseup(function (e){ var customButton = $('.nav-popover.profile'); if(!$('.custom-menu-caret').is(e.target) && $('.custom-menu-caret').has(e.target).length === 0){ if(!customButton.is(e.target) && customButton.has(e.target).length === 0){ if (!$('.custom-user-menu-v2 > .nav-link').is(e.target) && $('.custom-user-menu-v2 > .nav-link').has(e.target).length === 0) { customButton.removeClass('show'); } } } var menuWrapper = $('.menu-wrapper'); if(!menuWrapper.is(e.target) && menuWrapper.has(e.target).length === 0){ if (!$('.menu').is(e.target) && $('.menu').has(e.target).length === 0) { menuWrapper.removeClass('offcanvas'); } } var container = $(".plus-bar-main-content"); var customButton = $(".search-toggle-action-icon-plus"); if (!customButton.is(e.target) && customButton.has(e.target).length === 0) { container.hide(); } if(!$('.lang-picker-wrapper').is(e.target) && $('.lang-picker-wrapper').has(e.target).length === 0){ if (!$('.lang-picker').is(e.target) && $('.lang-picker').has(e.target).length === 0) { $('.lang-picker').removeClass('show'); } } }); //SCROLL JS $(window).scroll(function(e) { e.preventDefault(); if($('.nav-popover.profile').hasClass("show")){ if ($(this).scrollTop() > 0) { $('.nav-popover.profile').removeClass("show"); } else { $('.nav-popover.profile').addClass("show"); } } if($('.nav-popover.get-hubspot').hasClass("show")){ if ($(this).scrollTop() > 0) { $('.nav-popover.get-hubspot').removeClass("show"); } else { $('.nav-popover.get-hubspot').addClass("show"); } } if ($(this).scrollTop() > 0) { $('.search-input.lia-search-input-message').blur(); $('.plus-bar-main-content').hide(); } }); }); jQuery('.lang-picker-wrapper').click(function(){ jQuery(".lang-picker").toggleClass('show'); }); jQuery('.lia-cat-sub-editor-modal .lia-ui-modal-footer .lia-button-Submit-action').live('click',function(){ setTimeout( function() { location.reload(true); },1000); }); })(LITHIUM.jQuery); ;(function($){ var langMap = { 'en':'hubspot_community_en', 'es':'hubspot_community_es', 'fr':'hubspot_community_fr', 'ja':'hubspot_community_jp', 'pt-br':'hubspot_community_pt', 'de':'hubspot_community_de' } var nodeType = "board"; var langScope = langMap['en']; var isSearchPage = jQuery('body').hasClass('SearchPage'); var isIdeasLandingPage = jQuery('body').hasClass('ideaslandingpage'); if (nodeType === "community" && !isSearchPage && !isIdeasLandingPage) { var inputFormFilter = '<input name="filter" value="location" type="hidden">'; var inputFormLocation = '<input name="location" value="category:' + langScope + '" type="hidden">'; $('form.SearchForm').append(inputFormFilter).append(inputFormLocation); } else if (nodeType === "community" && isIdeasLandingPage) { var searchUrl = "/t5/forums/searchpage/tab/message?filter=location&location=idea-board:HubSpot_Ideas&collapse_discussion=true"; var query = jQuery('.SearchForm .lia-search-input-message').val(); jQuery(document).on('submit', 'form.SearchForm', function(e) { e.preventDefault(); var newQ = "&q=" + document.querySelector('.SearchForm .lia-search-input-wrapper input.search-input').value; window.location = window.location.origin + searchUrl + newQ; }) } })(LITHIUM.jQuery) LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c976f108e4","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c976f108e4_0","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c976f108e4_1","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c976f108e4_2","feedbackSelector":".InfoMessage"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c9770cb46f', 'disableAutoComplete', '#ajaxfeedback_2349c976f108e4_0', 'LITHIUM:ajaxError', {}, '7XS8p-UMTI60YNqgHyZ9fmiETjeI7-3OVs0lDCfXuOc.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"vugoUnqTBfK2F_aH9VA5RQBllDwk9y177Mtm1RFktq8.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c9770cb46f\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":3},"inputSelector":"#messageSearchField_2349c976f108e4_0","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.messagesearchfield.messagesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c9771ca312', 'disableAutoComplete', '#ajaxfeedback_2349c976f108e4_0', 'LITHIUM:ajaxError', {}, 'Ocb_aOFhJmjw0xhQDWN1nkbEhiqoaDVI-mE8zZVdK2c.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"M60sFtDyci9ePDInZbfonjz7yunbrGjO2i0DSoj-KEA.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c9771ca312\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":3},"inputSelector":"#messageSearchField_2349c976f108e4_1","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.tkbmessagesearchfield.messagesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching for users...","emptyText":"No Matches","successText":"Users found:","defaultText":"Enter a user name or rank","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c9772b4c50', 'disableAutoComplete', '#ajaxfeedback_2349c976f108e4_0', 'LITHIUM:ajaxError', {}, 'pHzjaaNqnoQtPBNFKmpflg-EK2h1nuaGqhoOjEKLUkg.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"mvc1M3IvCHSfBM6h1Um-Ie6i73xdKV-xQIuCzELEAgc.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c9772b4c50\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#userSearchField_2349c976f108e4","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.usersearchfield.usersearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AjaxSupport({"ajaxOptionsParam":{"event":"LITHIUM:userExistsQuery","parameters":{"javascript.ignore_combine_and_minify":"true"}},"tokenId":"ajax","elementSelector":"#userSearchField_2349c976f108e4","action":"userExistsQuery","feedbackSelector":"#ajaxfeedback_2349c976f108e4_0","url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.usersearchfield:userexistsquery?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","ajaxErrorEventName":"LITHIUM:ajaxError","token":"IhlrkdUe4HYldVBsEQz1QnmOsuMCK1fL2wWXUU3o850."}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c9773aacb8', 'disableAutoComplete', '#ajaxfeedback_2349c976f108e4_0', 'LITHIUM:ajaxError', {}, 'DhBYndOs-JpEuItV6GQWmIh1ucX6dLJFSdYWtWelLaw.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"ChCq-1kiATvCPSyFHSOB7TaYEB-rfUuK19vDCSc6qtc.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c9773aacb8\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#noteSearchField_2349c976f108e4_0","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.notesearchfield.notesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c977475a9c', 'disableAutoComplete', '#ajaxfeedback_2349c976f108e4_0', 'LITHIUM:ajaxError', {}, 'TGISC6fPAJ2wKT3jJWmq_n8oTiPFHK7kBGrnqpWm-mM.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"K4_oXnBXYPstME_HDjs0jQI3U2tBc_YAZbe8a6s34_c.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c977475a9c\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#productSearchField_2349c976f108e4","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.productsearchfield.productsearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AjaxSupport.fromLink('#enableAutoComplete_2349c976f108e4', 'enableAutoComplete', '#ajaxfeedback_2349c976f108e4_0', 'LITHIUM:ajaxError', {}, 'CYz1xV_0o67o8si8XPzkU1JBgSEuKRsTxVyuqz_4vS4.', 'ajax'); LITHIUM.Tooltip({"bodySelector":"body#lia-body","delay":30,"enableOnClickForTrigger":false,"predelay":10,"triggerSelector":"#link_2349c976f108e4","tooltipContentSelector":"#link_2349c976f108e4_0-tooltip-element .content","position":["bottom","left"],"tooltipElementSelector":"#link_2349c976f108e4_0-tooltip-element","events":{"def":"focus mouseover keydown,blur mouseout keydown"},"hideOnLeave":true}); LITHIUM.HelpIcon({"selectors":{"helpIconSelector":".help-icon .lia-img-icon-help"}}); LITHIUM.SearchAutoCompleteToggle({"containerSelector":"#searchautocompletetoggle_2349c976f108e4","enableAutoCompleteSelector":".search-autocomplete-toggle-link","enableAutocompleteSuccessEvent":"LITHIUM:ajaxSuccess:enableAutoComplete","disableAutoCompleteSelector":".lia-autocomplete-toggle-off","disableAutocompleteSuccessEvent":"LITHIUM:ajaxSuccess:disableAutoComplete","autoCompleteSelector":".lia-autocomplete-input"}); LITHIUM.SearchForm({"asSearchActionIdSelector":".lia-as-search-action-id","useAutoComplete":true,"selectSelector":".lia-search-form-granularity","useClearSearchButton":false,"buttonSelector":".lia-button-searchForm-action","asSearchActionIdParamName":"as-search-action-id","formSelector":"#lia-searchformV32_2349c976f108e4","nodesModel":{"community-champion-opportunities|blog-board":{"title":"Search Blog: Community Champion Opportunities","inputSelector":".lia-search-input-message"},"advocacy|category":{"title":"Search Category: Community Champion Opportunities","inputSelector":".lia-search-input-message"},"user|user":{"title":"Users","inputSelector":".lia-search-input-user"},"mjmao93648|community":{"title":"Search Community: Community Champion Opportunities","inputSelector":".lia-search-input-message"}},"asSearchActionIdHeaderKey":"X-LI-AS-Search-Action-Id","inputSelector":"#messageSearchField_2349c976f108e4_0:not(.lia-js-hidden)","clearSearchButtonSelector":null}); (function($) { document.querySelector('a.login-link').classList.add('homepage-nav-login'); })(LITHIUM.jQuery); (function($) { if ( $('.lia-notification-feed-page-link').length ) { $('.lia-notification-feed-page-link').addClass('nav-notifs'); } if ( $('.private-notes-link').length ) { $('.private-notes-link').addClass('nav-mail'); } })(LITHIUM.jQuery); ;(function($){ $('.custom-search-focus').on('click', function() { $('.lia-search-input-message').focus(); }); })(LITHIUM.jQuery); // Pull in global jQuery reference if (document.querySelectorAll('.lia-component-admin-widget-moderation-manager')[0]) { document.querySelectorAll('.lia-component-admin-widget-moderation-manager')[0].href = "/t5/bizapps/page/tab/community%3Amoderation?filter=includeForums&sort_by=-topicPostDate&include_forums=true&collapse_discussion=true" } ;(function($) { $("#get-hubspot-free").click(function(){ $("#get-hubspot").toggleClass("show"); }); // Closes dropdown boxes when clicking outside of the box // click listener applied inline, function in script tag window.onclick = function(e) { if (e.target?.matches && !e.target?.matches('#get-hubspot-free')) { if (document.getElementById("get-hubspot")) { if (document.getElementById("get-hubspot").classList.contains('show')) { document.getElementById("get-hubspot").classList.remove('show'); } } } if (e.target?.matches && !e.target?.matches('#current-language')) { if (document.getElementById("lang-picker-global")) { if (document.getElementById("lang-picker-global").classList.contains('show')) { document.getElementById("lang-picker-global").classList.remove('show'); } } } }; $(window).scroll(function(){ if ($(this).scrollTop() > 65) { $('.forum-nav-bar').addClass('ch-sticky'); $('.community-header-nav').addClass('ch-space'); } else { $('.forum-nav-bar').removeClass('ch-sticky'); $('.community-header-nav').removeClass('ch-space');; } }); $('span.custom-menu-caret').on('click',function(){ $(this).siblings('.nav-popover.profile').toggleClass('show'); }); })(LITHIUM.jQuery); (function($) { $(document).ready(function(){ try{ var PostURl = '' if('board'==='board'){ var PostURl = '/t5/forums/postpage/board-id/community-champion-opportunities' }else if('board'==='category') { var PostURl = '/t5/forums/postpage/category-id/community-champion-opportunities'} var searchSelector = $('.custom-search-wrapper .lia-autocomplete-container .lia-autocomplete-footer'); var data = "<div class='search-post-buttons'><span>Can't find what you're looking for?</span> <a href='"+PostURl+"' class='btn btn-sm button-primary'><span>Ask A Question</span></a> </div>"; $(data).insertAfter(searchSelector[0]); }catch(err){ console.log(err); } }); })(LITHIUM.jQuery); (function($) { $(document).ready(function(){ $('.lia-quilt-idea-exchange-page-filtered-v2 .custom-v2-banner .search-input, .lia-quilt-idea-page-filtered .custom-v2-banner .search-input').attr('placeholder', 'Search for Ideas'); }); })(LITHIUM.jQuery); ;(function($){ var langMap = { 'en':'hubspot_community_en', 'es':'hubspot_community_es', 'fr':'hubspot_community_fr', 'ja':'hubspot_community_jp', 'pt-br':'hubspot_community_pt', 'de':'hubspot_community_de' } var nodeType = "board"; var langScope = langMap['en']; var isSearchPage = jQuery('body').hasClass('SearchPage'); var isIdeasLandingPage = jQuery('body').hasClass('ideaslandingpage'); if (nodeType === "community" && !isSearchPage && !isIdeasLandingPage) { var inputFormFilter = '<input name="filter" value="location" type="hidden">'; var inputFormLocation = '<input name="location" value="category:' + langScope + '" type="hidden">'; $('form.SearchForm').append(inputFormFilter).append(inputFormLocation); } else if (nodeType === "community" && isIdeasLandingPage) { var searchUrl = "/t5/forums/searchpage/tab/message?filter=location&location=idea-board:HubSpot_Ideas&collapse_discussion=true"; var query = jQuery('.SearchForm .lia-search-input-message').val(); jQuery(document).on('submit', 'form.SearchForm', function(e) { e.preventDefault(); var newQ = "&q=" + document.querySelector('.SearchForm .lia-search-input-wrapper input.search-input').value; window.location = window.location.origin + searchUrl + newQ; }) } })(LITHIUM.jQuery) LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c97941611b","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c97941611b_0","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c97941611b_1","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c97941611b_2","feedbackSelector":".InfoMessage"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c97961c6d5', 'disableAutoComplete', '#ajaxfeedback_2349c97941611b_0', 'LITHIUM:ajaxError', {}, 'oQjiWjvNbWQJWxkSTGReVIUOQT5ntseYBIKRWnU7cXY.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"PMkACEvex4dWgcGdw855w0oUg6lo9BN9pissXS7X-D0.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c97961c6d5\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":3},"inputSelector":"#messageSearchField_2349c97941611b_0","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.messagesearchfield.messagesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c979742761', 'disableAutoComplete', '#ajaxfeedback_2349c97941611b_0', 'LITHIUM:ajaxError', {}, '7gFwlL4RAGiA9UaKmMtIHkUhGNACZwPfB6Jgfqw51vg.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"dUhGFmARWntaF4d7F5E1PtFiwxOwoVsWnlsU4aRF6TY.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c979742761\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":3},"inputSelector":"#messageSearchField_2349c97941611b_1","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.tkbmessagesearchfield.messagesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching for users...","emptyText":"No Matches","successText":"Users found:","defaultText":"Enter a user name or rank","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c97984cd3a', 'disableAutoComplete', '#ajaxfeedback_2349c97941611b_0', 'LITHIUM:ajaxError', {}, '37suRM3JW6SoTE035a1DGa2hwGRx7vf6hy4rUgy3B0o.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"fTFc2YBF9igs9p-9azEnP7N2ZZW0isTnllL_e4mluIY.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c97984cd3a\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#userSearchField_2349c97941611b","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.usersearchfield.usersearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AjaxSupport({"ajaxOptionsParam":{"event":"LITHIUM:userExistsQuery","parameters":{"javascript.ignore_combine_and_minify":"true"}},"tokenId":"ajax","elementSelector":"#userSearchField_2349c97941611b","action":"userExistsQuery","feedbackSelector":"#ajaxfeedback_2349c97941611b_0","url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.usersearchfield:userexistsquery?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","ajaxErrorEventName":"LITHIUM:ajaxError","token":"M9pFTLnTwenOk3jc59x18bXLAuFy_akPryXLdpINdTI."}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c979973d0c', 'disableAutoComplete', '#ajaxfeedback_2349c97941611b_0', 'LITHIUM:ajaxError', {}, 'CCk52YIIqbhCb2PXVo4jimqBwHr-7sMcqH60FtopeXA.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"_d3RI8HFbVDfRgTBJq0irj8AVVY_8q_WHqjCi2IjN0Y.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c979973d0c\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#noteSearchField_2349c97941611b_0","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.notesearchfield.notesearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AutoComplete({"options":{"triggerTextLength":0,"updateInputOnSelect":true,"loadingText":"Searching...","emptyText":"No Matches","successText":"Results:","defaultText":"Enter a search word","disabled":false,"footerContent":[{"scripts":"\n\n;(function($){LITHIUM.Link=function(params){var $doc=$(document);function handler(event){var $link=$(this);var token=$link.data('lia-action-token');if($link.data('lia-ajax')!==true&&token!==undefined){if(event.isPropagationStopped()===false&&event.isImmediatePropagationStopped()===false&&event.isDefaultPrevented()===false){event.stop();var $form=$('<form>',{method:'POST',action:$link.attr('href'),enctype:'multipart/form-data'});var $ticket=$('<input>',{type:'hidden',name:'lia-action-token',value:token});$form.append($ticket);$(document.body).append($form);$form.submit();$doc.trigger('click');}}}\nif($doc.data('lia-link-action-handler')===undefined){$doc.data('lia-link-action-handler',true);$doc.on('click.link-action',params.linkSelector,handler);$.fn.on=$.wrap($.fn.on,function(proceed){var ret=proceed.apply(this,$.makeArray(arguments).slice(1));if(this.is(document)){$doc.off('click.link-action',params.linkSelector,handler);proceed.call(this,'click.link-action',params.linkSelector,handler);}\nreturn ret;});}}})(LITHIUM.jQuery);\r\n\nLITHIUM.Link({\n \"linkSelector\" : \"a.lia-link-ticket-post-action\"\n});LITHIUM.AjaxSupport.fromLink('#disableAutoComplete_2349c979a54923', 'disableAutoComplete', '#ajaxfeedback_2349c97941611b_0', 'LITHIUM:ajaxError', {}, 'NRcCyluo1HmPZh18Uq7EueBisomz7iNAhJz3au_ix_0.', 'ajax');","content":"<a class=\"lia-link-navigation lia-autocomplete-toggle-off lia-link-ticket-post-action lia-component-search-action-disable-auto-complete\" data-lia-action-token=\"-3J8-A9vJwO4PyljYDslBzo3ajIq2VOy3upuG9seD50.\" rel=\"nofollow\" id=\"disableAutoComplete_2349c979a54923\" href=\"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.disableautocomplete:disableautocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=action/contributions/searchactions\">Turn off suggestions<\/a>"}],"prefixTriggerTextLength":0},"inputSelector":"#productSearchField_2349c97941611b","redirectToItemLink":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.searchformv32.productsearchfield.productsearchfield:autocomplete?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=search/contributions/page","resizeImageEvent":"LITHIUM:renderImages"}); LITHIUM.AjaxSupport.fromLink('#enableAutoComplete_2349c97941611b', 'enableAutoComplete', '#ajaxfeedback_2349c97941611b_0', 'LITHIUM:ajaxError', {}, 'aUkWaEE0CE6WG5HzngXkunR5uCCpbrWnHW4Ftgc7r5w.', 'ajax'); LITHIUM.Tooltip({"bodySelector":"body#lia-body","delay":30,"enableOnClickForTrigger":false,"predelay":10,"triggerSelector":"#link_2349c97941611b","tooltipContentSelector":"#link_2349c97941611b_0-tooltip-element .content","position":["bottom","left"],"tooltipElementSelector":"#link_2349c97941611b_0-tooltip-element","events":{"def":"focus mouseover keydown,blur mouseout keydown"},"hideOnLeave":true}); LITHIUM.HelpIcon({"selectors":{"helpIconSelector":".help-icon .lia-img-icon-help"}}); LITHIUM.SearchAutoCompleteToggle({"containerSelector":"#searchautocompletetoggle_2349c97941611b","enableAutoCompleteSelector":".search-autocomplete-toggle-link","enableAutocompleteSuccessEvent":"LITHIUM:ajaxSuccess:enableAutoComplete","disableAutoCompleteSelector":".lia-autocomplete-toggle-off","disableAutocompleteSuccessEvent":"LITHIUM:ajaxSuccess:disableAutoComplete","autoCompleteSelector":".lia-autocomplete-input"}); LITHIUM.SearchForm({"asSearchActionIdSelector":".lia-as-search-action-id","useAutoComplete":true,"selectSelector":".lia-search-form-granularity","useClearSearchButton":false,"buttonSelector":".lia-button-searchForm-action","asSearchActionIdParamName":"as-search-action-id","formSelector":"#lia-searchformV32_2349c97941611b","nodesModel":{"community-champion-opportunities|blog-board":{"title":"Search Blog: Community Champion Opportunities","inputSelector":".lia-search-input-message"},"advocacy|category":{"title":"Search Category: Community Champion Opportunities","inputSelector":".lia-search-input-message"},"user|user":{"title":"Users","inputSelector":".lia-search-input-user"},"mjmao93648|community":{"title":"Search Community: Community Champion Opportunities","inputSelector":".lia-search-input-message"}},"asSearchActionIdHeaderKey":"X-LI-AS-Search-Action-Id","inputSelector":"#messageSearchField_2349c97941611b_0:not(.lia-js-hidden)","clearSearchButtonSelector":null}); LITHIUM.DropDownMenu({"userMessagesFeedOptionsClass":"div.user-messages-feed-options-menu a.lia-js-menu-opener","menuOffsetContainer":".lia-menu-offset-container","hoverLeaveEvent":"LITHIUM:hoverLeave","mouseoverElementSelector":".lia-js-mouseover-menu","userMessagesFeedOptionsAriaLabel":"Show contributions of the user, selected option is Options. You may choose another option from the dropdown menu.","disabledLink":"lia-link-disabled","menuOpenCssClass":"dropdownHover","menuElementSelector":".lia-menu-navigation-wrapper","dialogSelector":".lia-panel-dialog-trigger","messageOptions":"lia-component-message-view-widget-action-menu","menuBarComponent":"lia-component-menu-bar","closeMenuEvent":"LITHIUM:closeMenu","menuOpenedEvent":"LITHIUM:menuOpened","pageOptions":"lia-component-community-widget-page-options","clickElementSelector":".lia-js-click-menu","menuItemsSelector":".lia-menu-dropdown-items","menuClosedEvent":"LITHIUM:menuClosed"}); LITHIUM.DropDownMenuVisibilityHandler({"selectors":{"menuSelector":"#actionMenuDropDown_2349c979c96046","menuItemsSelector":".lia-menu-dropdown-items"}}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c97a66c7d3","feedbackSelector":".InfoMessage"}); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_2349c97a66c7d3_0","feedbackSelector":".InfoMessage"}); (function($){ $('.lia-link-navigation + .lia-user-rank-icon-right').wrap('<span class="img-wrapper"></span>'); })(LITHIUM.jQuery); LITHIUM.DropDownMenu({"userMessagesFeedOptionsClass":"div.user-messages-feed-options-menu a.lia-js-menu-opener","menuOffsetContainer":".lia-menu-offset-container","hoverLeaveEvent":"LITHIUM:hoverLeave","mouseoverElementSelector":".lia-js-mouseover-menu","userMessagesFeedOptionsAriaLabel":"Show contributions of the user, selected option is Show Share your review of Adobe Express post option menu. You may choose another option from the dropdown menu.","disabledLink":"lia-link-disabled","menuOpenCssClass":"dropdownHover","menuElementSelector":".lia-menu-navigation-wrapper","dialogSelector":".lia-panel-dialog-trigger","messageOptions":"lia-component-message-view-widget-action-menu","menuBarComponent":"lia-component-menu-bar","closeMenuEvent":"LITHIUM:closeMenu","menuOpenedEvent":"LITHIUM:menuOpened","pageOptions":"lia-component-community-widget-page-options","clickElementSelector":".lia-js-click-menu","menuItemsSelector":".lia-menu-dropdown-items","menuClosedEvent":"LITHIUM:menuClosed"}); LITHIUM.DropDownMenuVisibilityHandler({"selectors":{"menuSelector":"#actionMenuDropDown_2349c97b40cddc","menuItemsSelector":".lia-menu-dropdown-items"}}); LITHIUM.MessageBodyDisplay('#bodyDisplay_2349c97a66c7d3', '.lia-truncated-body-container', '#viewMoreLink_2349c97a66c7d3', '.lia-full-body-container' ); LITHIUM.CustomEvent('.lia-custom-event', 'click'); LITHIUM.AjaxSupport.fromLink('#kudoEntity_2349c97a66c7d3', 'kudoEntity', '#ajaxfeedback_2349c97a66c7d3', 'LITHIUM:ajaxError', {}, 'MTwSf64HKwho3fv8y38CPuFtIfVCui0H_Mb4i0AzxWQ.', 'ajax'); LITHIUM.AjaxSupport.ComponentEvents.set({ "eventActions" : [ { "event" : "kudoEntity", "actions" : [ { "context" : "envParam:entity", "action" : "rerender" } ] } ], "componentId" : "kudos.widget.button", "initiatorBinding" : true, "selector" : "#kudosButtonV2_2349c97a66c7d3", "parameters" : { "displayStyle" : "horizontal", "disallowZeroCount" : "false", "revokeMode" : "true", "kudosable" : "true", "showCountOnly" : "false", "disableKudosForAnonUser" : "false", "useCountToKudo" : "false", "entity" : "1062774", "linkDisabled" : "false" }, "initiatorDataMatcher" : "data-lia-kudos-id" }); LITHIUM.AjaxSupport.ComponentEvents.set({ "eventActions" : [ { "event" : "approveMessage", "actions" : [ { "context" : "", "action" : "rerender" }, { "context" : "", "action" : "pulsate" } ] }, { "event" : "unapproveMessage", "actions" : [ { "context" : "", "action" : "rerender" }, { "context" : "", "action" : "pulsate" } ] }, { "event" : "deleteMessage", "actions" : [ { "context" : "lia-deleted-state", "action" : "addClassName" }, { "context" : "", "action" : "pulsate" } ] }, { "event" : "QuickReply", "actions" : [ { "context" : "envParam:feedbackData", "action" : "rerender" } ] }, { "event" : "expandMessage", "actions" : [ { "context" : "envParam:quiltName,expandedQuiltName", "action" : "rerender" } ] }, { "event" : "ProductAnswer", "actions" : [ { "context" : "envParam:quiltName", "action" : "rerender" } ] }, { "event" : "ProductAnswerComment", "actions" : [ { "context" : "envParam:selectedMessage", "action" : "rerender" } ] }, { "event" : "editProductMessage", "actions" : [ { "context" : "envParam:quiltName,message", "action" : "rerender" } ] }, { "event" : "MessagesWidgetEditAction", "actions" : [ { "context" : "envParam:quiltName,message,product,contextId,contextUrl", "action" : "rerender" } ] }, { "event" : "ProductMessageEdit", "actions" : [ { "context" : "envParam:quiltName", "action" : "rerender" } ] }, { "event" : "MessagesWidgetMessageEdit", "actions" : [ { "context" : "envParam:quiltName,product,contextId,contextUrl", "action" : "rerender" } ] }, { "event" : "AcceptSolutionAction", "actions" : [ { "context" : "", "action" : "rerender" } ] }, { "event" : "RevokeSolutionAction", "actions" : [ { "context" : "", "action" : "rerender" } ] }, { "event" : "addThreadUserEmailSubscription", "actions" : [ { "context" : "", "action" : "rerender" } ] }, { "event" : "removeThreadUserEmailSubscription", "actions" : [ { "context" : "", "action" : "rerender" } ] }, { "event" : "addMessageUserEmailSubscription", "actions" : [ { "context" : "", "action" : "rerender" } ] }, { "event" : "removeMessageUserEmailSubscription", "actions" : [ { "context" : "", "action" : "rerender" } ] }, { "event" : "markAsSpamWithoutRedirect", "actions" : [ { "context" : "", "action" : "rerender" } ] }, { "event" : "MessagesWidgetAnswerForm", "actions" : [ { "context" : "envParam:messageUid,page,quiltName,product,contextId,contextUrl", "action" : "rerender" } ] }, { "event" : "MessagesWidgetEditAnswerForm", "actions" : [ { "context" : "envParam:messageUid,quiltName,product,contextId,contextUrl", "action" : "rerender" } ] }, { "event" : "MessagesWidgetCommentForm", "actions" : [ { "context" : "envParam:messageUid,quiltName,product,contextId,contextUrl", "action" : "rerender" } ] }, { "event" : "MessagesWidgetEditCommentForm", "actions" : [ { "context" : "envParam:messageUid,quiltName,product,contextId,contextUrl", "action" : "rerender" } ] } ], "componentId" : "forums.widget.message-view", "initiatorBinding" : true, "selector" : "#messageview_2349c97a66c7d3", "parameters" : { "disableLabelLinks" : "false", "truncateBodyRetainsHtml" : "false", "forceSearchRequestParameterForBlurbBuilder" : "false", "kudosLinksDisabled" : "false", "useSubjectIcons" : "true", "quiltName" : "BlogTopicMessage", "truncateBody" : "true", "message" : "1062774", "includeRepliesModerationState" : "false", "useSimpleView" : "false", "useTruncatedSubject" : "true", "disableLinks" : "false", "messageViewOptions" : "1111110111111100111111101110100101111101", "displaySubject" : "true" }, "initiatorDataMatcher" : "data-lia-message-uid" }); LITHIUM.InformationBox({"updateFeedbackEvent":"LITHIUM:updateAjaxFeedback","componentSelector":"#informationbox_0_2349c97da67c2e","feedbackSelector":".InfoMessage"}); LITHIUM.Components.renderInPlace('recommendations.widget.recommended-content-taplet', {"componentParams":"{\n \"mode\" : \"default\",\n \"componentId\" : \"recommendations.widget.recommended-content-taplet\",\n \"id\" : \"recommendations.widget.recommended-content-taplet\",\n \"skipInlineEditMode\" : \"true\"\n}","componentId":"recommendations.widget.recommended-content-taplet"}, {"errorMessage":"An Unexpected Error has occurred.","type":"POST","url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.recommendedcontenttaplet:lazyrender?t:ac=blog-id/community-champion-opportunities/article-id/55&t:cp=recommendations/contributions/page"}, 'lazyload'); (function($) { $(window).on('load', function() { $('.author-info-wrapper').each(function(){ $(this).find('.UserAvatar img').css("width","40px"); var href = $(this).find('.lia-link-navigation').attr('href'); var img = $(this).find('.UserAvatar').html(); $(this).find('.UserAvatar').html('<a class="lia-user-name-link" href="'+href+'" aria-describedby="userProfileCard-102">'+img+'</a>'); }); $('.lia-user-name-link span').addClass('login-bold'); }); })(LITHIUM.jQuery); (function($) { $(document).ready(function() { var pageName = 'BlogArticlePage'; var reply = window.location.hash.substr(1); if(reply =='reply') { if( pageName == 'BlogArticlePage') { $('.MessageEditor .lia-quilt-row.lia-quilt-row-standard').attr('id', 'reply'); $( window ).on( "load", function() { $('iframe').trigger("click"); }); } else { $('.lia-form-type-text.lia-inline-topic-div').attr('id', 'reply').trigger("click"); $('.lia-quilt-row.lia-quilt-row-standard.lia-input-edit-form-row').focus(); } } $('.lia-form-modbarwhats-hot-input').change(function(){ let valueHotsSel = $(this).find('option:selected').attr('description'); let currentFormId = $(this).closest('form').attr('id'); let currentForm = '#'+currentFormId+' input[name=form_instance_key]'; let CurrentIdMsg = $(currentForm).val(); console.log(CurrentIdMsg); $.get('/plugins/custom/hubspot/hubspot/custom-tags-rearrange?Msgid='+CurrentIdMsg+'&statusVal='+valueHotsSel) .done(function(data) { console.log(data); }).fail(function(err) { console.log(err); }) }); }); })(LITHIUM.jQuery); ;(function ($) { if ($(window).width() <= 991) { $(".community-footer .col:nth-child(3)").on('click',function(){ if ($(this).hasClass('active')) { $(this).removeClass('active'); $(this).children("ul").hide(); $(this).children("h5").removeClass("addedClass"); } else { $(".community-footer .col").removeClass('active'); $('.community-footer .col ul').hide(); $(this).addClass('active'); $(this).children("ul").show(); $('.community-footer .col').children("h5").removeClass("addedClass"); $(this).children("h5").addClass("addedClass"); } if ( $(".community-footer .col:nth-child(4) ul").hasClass('custom-footer-res')) { $(".community-footer .col:nth-child(4) ul").removeClass('custom-footer-res'); } else { $(".community-footer .col:nth-child(4) ul").addClass('custom-footer-res'); } }) $('.community-footer .col:nth-child(1)').on("click", function () { if ($(this).hasClass('active')) { $(this).removeClass('active'); $(this).children("ul").hide(); $(this).children("h5").removeClass("addedClass"); } else { $(".community-footer .col").removeClass('active'); $('.community-footer .col ul').hide(); $(this).addClass('active'); $(this).children("ul").show(); $('.community-footer .col').children("h5").removeClass("addedClass"); $(this).children("h5").addClass("addedClass"); } if ( $(".community-footer .col:nth-child(4) ul").hasClass('custom-footer-res')) { $(".community-footer .col:nth-child(4) ul").removeClass('custom-footer-res'); } }) $('.community-footer .col:nth-child(2)').on("click", function () { if ($(this).hasClass('active')) { $(this).removeClass('active'); $(this).children("ul").hide(); $(this).children("h5").removeClass("addedClass"); } else { $(".community-footer .col").removeClass('active'); $('.community-footer .col ul').hide(); $(this).addClass('active'); $(this).children("ul").show(); $('.community-footer .col').children("h5").removeClass("addedClass"); $(this).children("h5").addClass("addedClass"); } if ( $(".community-footer .col:nth-child(4) ul").hasClass('custom-footer-res')) { $(".community-footer .col:nth-child(4) ul").removeClass('custom-footer-res'); } }) } })(LITHIUM.jQuery); LITHIUM.PartialRenderProxy({"limuirsComponentRenderedEvent":"LITHIUM:limuirsComponentRendered","relayEvent":"LITHIUM:partialRenderProxyRelay","listenerEvent":"LITHIUM:partialRenderProxy"}); LITHIUM.AjaxSupport({"ajaxOptionsParam":{"event":"LITHIUM:partialRenderProxyRelay","parameters":{"javascript.ignore_combine_and_minify":"true"}},"tokenId":"ajax","elementSelector":document,"action":"partialRenderProxyRelay","feedbackSelector":false,"url":"https://community.hubspot.com/t5/blogs/v2/blogarticlepage.liabase.basebody.partialrenderproxy:partialrenderproxyrelay?t:ac=blog-id/community-champion-opportunities/article-id/55","ajaxErrorEventName":"LITHIUM:ajaxError","token":"nCFQcWb5zDBpd00OHF2qgoh05bJNo-24ULFb9sxTug8."}); LITHIUM.Auth.API_URL = "/t5/util/authcheckpage"; LITHIUM.Auth.LOGIN_URL_TMPL = "https://app.hubspot.com/khoros/integration/jwt/authenticate?referer=https%3A%2F%2FREPLACE_TEXT"; LITHIUM.Auth.KEEP_ALIVE_URL = "/t5/status/blankpage?keepalive"; LITHIUM.Auth.KEEP_ALIVE_TIME = 300000; LITHIUM.Auth.CHECK_SESSION_TOKEN = 'HOpjW7EdgQB6ogqvvbpsMwOC17aVbC-GHFRpvWw4Ymw.'; LITHIUM.AjaxSupport.useTickets = false; LITHIUM.Cache.CustomEvent.set([{"elementId":"link_2349c97a66c7d3_0","stopTriggerEvent":false,"fireEvent":"LITHIUM:labelSelected","triggerEvent":"click","eventContext":{"uid":3788,"selectedLabel":"brand endorsement","title":"Brand Endorsement"}}]); LITHIUM.Loader.runJsAttached(); // --> </script></body> </html>