npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

supernal-linkedin-formatter

v1.0.2

Published

Framework-agnostic Markdown to LinkedIn formatter with pure algorithms, React hooks, and copy-paste widgets

Downloads

13

Readme

@supernal-social/linkedin-formatter

Framework-agnostic Markdown to LinkedIn formatter with pure algorithms, optional React hooks, and copy-paste widgets.

🎯 Design Philosophy

This package is designed for maximum reusability:

  1. Pure algorithms - No framework dependencies in core
  2. Optional React hooks - Use in any React framework
  3. Copy-paste widgets - Ready-made components you can customize
  4. Zero config - Works out of the box

📦 Installation

npm install @supernal-social/linkedin-formatter
# or
pnpm add @supernal-social/linkedin-formatter
# or
yarn add @supernal-social/linkedin-formatter

🚀 Quick Start

1. Pure JavaScript (Works Anywhere)

// Node.js, browser, any framework
import { markdownToLinkedIn } from '@supernal-social/linkedin-formatter';

const formatted = markdownToLinkedIn('**Hello LinkedIn!**');
console.log(formatted); // 𝐇𝐞𝐥𝐥𝐨 𝐋𝐢𝐧𝐤𝐞𝐝𝐈𝐧!

2. React Hook (Any React Framework)

import { useLinkedInFormatter } from '@supernal-social/linkedin-formatter/react';

function MyComponent() {
  const { formatted, convert, copyToClipboard, charCount } = useLinkedInFormatter();
  
  return (
    <div>
      <textarea onChange={(e) => convert(e.target.value)} />
      <pre>{formatted}</pre>
      <button onClick={copyToClipboard}>Copy</button>
      <span>{charCount.remaining} chars remaining</span>
    </div>
  );
}

3. Ready-Made Widget (Drop In)

import LinkedInFormatter from '@supernal-social/linkedin-formatter/widgets';

export default function Page() {
  return <LinkedInFormatter />;
}

📚 Usage Patterns

Pattern 1: Pure Algorithm (Framework-Agnostic)

Best for: Integrating into existing systems, CLI tools, serverless functions

import { 
  markdownToLinkedIn, 
  getCharacterCount, 
  checkAccessibility,
  getPlainText 
} from '@supernal-social/linkedin-formatter';

// Convert
const formatted = markdownToLinkedIn('**Bold** and *italic*');

// Check character count
const count = getCharacterCount(formatted);
if (count.exceedsLimit) {
  console.warn(`Exceeds limit by ${Math.abs(count.remaining)} chars`);
}

// Check accessibility
const accessibility = checkAccessibility(formatted);
console.log(`Warnings: ${accessibility.warnings.length}`);

// Get plain text (strip Unicode)
const plain = getPlainText(formatted);

Pattern 2: React Hook (Custom UI)

Best for: Building your own UI with full control

import { useLinkedInFormatter } from '@supernal-social/linkedin-formatter/react';

function CustomFormatter() {
  const {
    formatted,
    plainText,
    charCount,
    accessibility,
    convert,
    copyToClipboard,
    clear,
  } = useLinkedInFormatter();

  return (
    <YourCustomUI
      onInputChange={convert}
      output={formatted}
      onCopy={copyToClipboard}
      stats={charCount}
      warnings={accessibility.warnings}
    />
  );
}

Pattern 3: Copy-Paste Widget (Fastest)

Best for: Quick implementation, prototyping, tools pages

// Option A: Use pre-built widget
import { BasicFormatter } from '@supernal-social/linkedin-formatter/widgets';

export default function Page() {
  return <BasicFormatter />;
}

// Option B: Copy widget file and customize
// Copy: node_modules/@supernal-social/linkedin-formatter/src/widgets/basic.tsx
// To: your-project/components/LinkedInFormatter.tsx
// Customize to your needs!

🎨 Available Widgets

| Widget | Size | Features | Use Case | |--------|------|----------|----------| | Minimal | 50 LOC | Input/Output only | Integration into existing UI | | Basic | 150 LOC | + Copy + Counter | Quick tools page | | NextJs | 200 LOC | + Examples + Stats | Full Next.js page |

Minimal Widget (50 lines)

import { MinimalFormatter } from '@supernal-social/linkedin-formatter/widgets';
// Pure input/output, no styling

Basic Widget (150 lines)

import { BasicFormatter } from '@supernal-social/linkedin-formatter/widgets';
// Two-panel layout, copy button, character counter, Tailwind CSS

Next.js Widget (200 lines)

import { NextJsFormatterPage } from '@supernal-social/linkedin-formatter/widgets';
// Full-featured with examples, stats, warnings, responsive design

🔧 Integration Examples

In Next.js App Router

// app/tools/linkedin-formatter/page.tsx
import { NextJsFormatterPage } from '@supernal-social/linkedin-formatter/widgets';

export default function LinkedInFormatterPage() {
  return <NextJsFormatterPage />;
}

export const metadata = {
  title: 'LinkedIn Formatter',
  description: 'Convert Markdown to LinkedIn text'
};

In Next.js Pages Router

// pages/tools/linkedin-formatter.tsx
import { BasicFormatter } from '@supernal-social/linkedin-formatter/widgets';

export default function LinkedInFormatterPage() {
  return (
    <div className="container mx-auto py-12">
      <BasicFormatter />
    </div>
  );
}

In Existing Dashboard

// In your existing dashboard component
import { useLinkedInFormatter } from '@supernal-social/linkedin-formatter/react';

export function DashboardSection() {
  const { formatted, convert } = useLinkedInFormatter();
  
  return (
    <YourExistingLayout>
      <YourExistingInput onChange={(text) => convert(text)} />
      <YourExistingOutput value={formatted} />
    </YourExistingLayout>
  );
}

As Standalone API

// API route or serverless function
import { markdownToLinkedIn } from '@supernal-social/linkedin-formatter';

export async function POST(req: Request) {
  const { markdown } = await req.json();
  const formatted = markdownToLinkedIn(markdown);
  return Response.json({ formatted });
}

📖 API Reference

Core Functions

markdownToLinkedIn(markdown: string, options?: ConversionOptions): string

Convert Markdown to LinkedIn-formatted text.

const formatted = markdownToLinkedIn('**Bold** text', {
  preserveLinks: true,      // Keep URLs in links
  bulletStyle: '•',         // Bullet character
  headingStyle: 'both',     // 'bold' | 'uppercase' | 'both'
  codeBlockStyle: 'monospace' // 'monospace' | 'backticks'
});

getCharacterCount(text: string): CharacterCountInfo

Get detailed character count information.

const count = getCharacterCount(formatted);
// {
//   total: 150,
//   plain: 145,
//   unicode: 20,
//   linkedInLimit: 3000,
//   remaining: 2850,
//   exceedsLimit: false
// }

checkAccessibility(text: string): AccessibilityReport

Check for accessibility issues.

const accessibility = checkAccessibility(formatted);
// {
//   hasUnicodeFormatting: true,
//   unicodeCharCount: 20,
//   plainTextLength: 145,
//   unicodePercentage: 13.3,
//   warnings: ['Screen readers may not...', ...]
// }

getPlainText(formatted: string): string

Strip all Unicode formatting.

const plain = getPlainText('𝐁𝐨𝐥𝐝');
// 'Bold'

React Hooks

useLinkedInFormatter(initialMarkdown?: string, defaultOptions?: ConversionOptions)

Complete hook with state management.

const {
  formatted,        // Formatted output
  plainText,        // Plain text version
  accessibility,    // Accessibility report
  charCount,        // Character count info
  convert,          // (markdown, options?) => void
  copyToClipboard,  // () => Promise<void>
  copyPlainToClipboard, // () => Promise<void>
  clear,            // () => void
} = useLinkedInFormatter();

useLinkedInConvert(markdown: string, options?: ConversionOptions): string

Simple stateless conversion.

const [text, setText] = useState('');
const formatted = useLinkedInConvert(text);

🎯 Supported Markdown

| Syntax | Example | Output | |--------|---------|--------| | Bold | **text** | 𝐛𝐨𝐥𝐝 | | Italic | *text* | 𝑖𝑡𝑎𝑙𝑖𝑐 | | Code | `text` | 𝚌𝚘𝚍𝚎 | | Strikethrough | ~~text~~ | t̶e̶x̶t̶ | | Heading | # Title | 𝐓𝐈𝐓𝐋𝐄 | | List | - item | • item | | Link | [text](url) | text (url) | | Code Block | ```code``` | 𝚌𝚘𝚍𝚎 𝚋𝚕𝚘𝚌𝚔 |

🏗️ Package Structure

@supernal-social/linkedin-formatter/
├── index                 # Core algorithms (no dependencies)
├── react                 # React hooks (optional)
└── widgets               # Ready-made components (copy-paste)

Import Paths

// Core (works anywhere)
import { markdownToLinkedIn } from '@supernal-social/linkedin-formatter';

// React hooks (any React framework)
import { useLinkedInFormatter } from '@supernal-social/linkedin-formatter/react';

// Widgets (ready-made components)
import LinkedInFormatter from '@supernal-social/linkedin-formatter/widgets';
import { BasicFormatter } from '@supernal-social/linkedin-formatter/widgets';
import { MinimalFormatter } from '@supernal-social/linkedin-formatter/widgets';
import { NextJsFormatterPage } from '@supernal-social/linkedin-formatter/widgets';

🎨 Customization

Customize a Widget

  1. Copy the widget file you want
  2. Modify to match your design system
  3. Use it as your own component
# Copy basic widget
cp node_modules/@supernal-social/linkedin-formatter/src/widgets/basic.tsx \
   src/components/MyLinkedInFormatter.tsx

# Customize and use
import MyLinkedInFormatter from '@/components/MyLinkedInFormatter';

Build Your Own Widget

import { useLinkedInFormatter } from '@supernal-social/linkedin-formatter/react';

export function MyCustomWidget() {
  const { formatted, convert, charCount } = useLinkedInFormatter();
  
  // Your custom UI with your design system
  return <YourCustomDesign />;
}

⚠️ Accessibility

The package includes built-in accessibility checking:

const { warnings } = checkAccessibility(formatted);
// [
//   'Screen readers may not properly read Unicode-styled text',
//   'Search engines may not index styled text properly',
//   ...
// ]

Best Practices:

  • Use formatting sparingly (< 30% of text)
  • Provide plain text alternative
  • Don't format critical information (names, dates)
  • Include accessibility warnings in your UI

🧪 Testing

cd node_modules/@supernal-social/linkedin-formatter
node test.js

📄 License

MIT - Free to use, modify, and distribute

🤝 Contributing

This package is designed to be forked and customized. Feel free to:

  • Copy widgets and modify them
  • Build new widgets on top of the hooks
  • Extend the core algorithms
  • Create integrations for other frameworks

📚 Examples

See src/widgets/ for complete, working examples that you can copy and customize.


Made with ❤️ by Supernal Intelligence

Links: